From 3c19b97b7a04af6d443215f003fbb00480ed9c89 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 31 Dec 2020 15:59:10 +0900 Subject: [PATCH 001/443] Enable all stable features in the playground --- Cargo.toml | 3 +++ rand_core/Cargo.toml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index aee917c1226..c1186158800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,3 +85,6 @@ bincode = "1.2.1" # RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open all-features = true rustdoc-args = ["--cfg", "doc_cfg"] + +[package.metadata.playground] +features = ["small_rng", "serde1"] diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index a53a1215f46..7f8a17d49f9 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -28,3 +28,6 @@ getrandom = { version = "0.2", optional = true } # RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open all-features = true rustdoc-args = ["--cfg", "doc_cfg"] + +[package.metadata.playground] +all-features = true From a63eb3ad3605bee74ef8d61892d07e99d5bd88b1 Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 31 Dec 2020 23:06:56 +0900 Subject: [PATCH 002/443] Prepare rand_core 0.6.1 --- rand_core/CHANGELOG.md | 4 ++++ rand_core/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index 63a8bb8f694..b502f9bc49e 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.1] - 2020-12-31 +### Other +- Enable all stable features in the playground (#1081) + ## [0.6.0] - 2020-12-08 ### Breaking changes - Bump MSRV to 1.36, various code improvements (#1011) diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 7f8a17d49f9..bfb877dd7ea 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.6.0" +version = "0.6.1" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From e5eec8e650cf97533658a0afb6a6fbd0b3bc988d Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 31 Dec 2020 23:10:13 +0900 Subject: [PATCH 003/443] Prepare rand 0.8.1 --- CHANGELOG.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4815bbb83c..b53f95e2064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [0.8.1] - 2020-12-31 +### Other +- Enable all stable features in the playground (#1081) + ## [0.8.0] - 2020-12-18 ### Platform support - The minimum supported Rust version is now 1.36 (#1011) diff --git a/Cargo.toml b/Cargo.toml index c1186158800..512ed589ac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.8.0" +version = "0.8.1" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From eb4b8a44d3b5b52ef142345a89bf9eb6b3c77ec6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 1 Jan 2021 14:34:38 +0000 Subject: [PATCH 004/443] Fix #1082 (seed_from_u64 with non multiple of 4) --- rand_core/CHANGELOG.md | 5 ++++- rand_core/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index b502f9bc49e..c0c2418f9b2 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,7 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.6.1] - 2020-12-31 +## [0.6.1] - 2021-01-03 +### Fixed +- Avoid panic when using `RngCore::seed_from_u64` with a seed which is not a + multiple of four (#1082) ### Other - Enable all stable features in the playground (#1081) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index ff553a335ae..fdf82dd5e35 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -313,7 +313,7 @@ pub trait SeedableRng: Sized { let xorshifted = (((state >> 18) ^ state) >> 27) as u32; let rot = (state >> 59) as u32; let x = xorshifted.rotate_right(rot); - chunk.copy_from_slice(&x.to_le_bytes()); + chunk.copy_from_slice(&x.to_le_bytes()[..chunk.len()]); } Self::from_seed(seed) From 594aed829133a9df8219fd2a2670c631a03d5b19 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 2 Jan 2021 10:17:05 +0000 Subject: [PATCH 005/443] seed_from_u64: use newpavlov's suggestion --- rand_core/src/lib.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index fdf82dd5e35..7e847ae499a 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -300,20 +300,30 @@ pub trait SeedableRng: Sized { /// considered a value-breaking change. fn seed_from_u64(mut state: u64) -> Self { // We use PCG32 to generate a u32 sequence, and copy to the seed - const MUL: u64 = 6364136223846793005; - const INC: u64 = 11634580027462260723; + fn pcg32(state: &mut u64) -> [u8; 4] { + const MUL: u64 = 6364136223846793005; + const INC: u64 = 11634580027462260723; - let mut seed = Self::Seed::default(); - for chunk in seed.as_mut().chunks_mut(4) { // We advance the state first (to get away from the input value, // in case it has low Hamming Weight). - state = state.wrapping_mul(MUL).wrapping_add(INC); + *state = state.wrapping_mul(MUL).wrapping_add(INC); + let state = *state; // Use PCG output function with to_le to generate x: let xorshifted = (((state >> 18) ^ state) >> 27) as u32; let rot = (state >> 59) as u32; let x = xorshifted.rotate_right(rot); - chunk.copy_from_slice(&x.to_le_bytes()[..chunk.len()]); + x.to_le_bytes() + } + + let mut seed = Self::Seed::default(); + let mut iter = seed.as_mut().chunks_exact_mut(4); + for chunk in &mut iter { + chunk.copy_from_slice(&pcg32(&mut state)); + } + let rem = iter.into_remainder(); + if !rem.is_empty() { + rem.copy_from_slice(&pcg32(&mut state)[..rem.len()]); } Self::from_seed(seed) From 4e8c7a4ca2963797d0ec414d04b6239ece905165 Mon Sep 17 00:00:00 2001 From: Gautier Minster Date: Tue, 12 Jan 2021 17:30:22 +0200 Subject: [PATCH 006/443] distributions/uniform: fix panic in gen_range(0..=MAX) This commit fixes a panic when generating a single sample in an inclusive range that spans the entire integer range, eg for u8: ```rust rng.gen_range(0..=u8::MAX) // panicked at 'attempt to add with overflow', src/distributions/uniform.rs:529:42 ``` [Playground example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&code=use%20rand%3A%3ARng%3B%0A%0Afn%20main()%20%7B%0A%20%20%20%20rand%3A%3Athread_rng().gen_range(0u8..%3D255u8)%3B%0A%7D). The cause is a discrepancy between the "single sample" and the "many samples" codepaths: ```rust // Ok UniformSampler::new_inclusive(u8::MIN, u8::MAX).sample(&mut rng); // Panic UniformSampler::sample_single_inclusive(u8::MIN, u8::MAX, &mut rng); ``` In `sample`, a `range` of 0 is interpreted to mean "sample from the whole range". In `sample_range_inclusive`, no check is performed, which leads to overflow when computing the `ints_to_reject`. **Testing** - Added a test case. - Old code panics, new code passes. --- src/distributions/uniform.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index bbd96948f8c..e4a6407fd66 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -521,6 +521,12 @@ macro_rules! uniform_int_impl { let high = *high_b.borrow(); assert!(low <= high, "UniformSampler::sample_single_inclusive: low > high"); let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned as $u_large; + // If the above resulted in wrap-around to 0, the range is $ty::MIN..=$ty::MAX, + // and any integer will do. + if range == 0 { + return rng.gen(); + } + let zone = if ::core::$unsigned::MAX <= ::core::u16::MAX as $unsigned { // Using a modulus is faster than the approximation for // i8 and i16. I suppose we trade the cost of one @@ -1235,6 +1241,11 @@ mod tests { let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng); assert!($le(low, v) && $lt(v, high)); } + + for _ in 0..1000 { + let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng); + assert!($le(low, v) && $le(v, high)); + } } }}; From 2c9085a2de8864ee327af0311f6a8e5747cf25b7 Mon Sep 17 00:00:00 2001 From: Gautier Minster Date: Tue, 12 Jan 2021 19:49:03 +0200 Subject: [PATCH 007/443] Bump to 0.8.2 and update changelog --- CHANGELOG.md | 5 +++++ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b53f95e2064..c0495305fd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [0.8.2] - 2021-01-12 +### Fixes +- Fix panic in `UniformInt::sample_single_inclusive` and `Rng::gen_range` when + providing a full integer range (eg `0..=MAX`) (#1087) + ## [0.8.1] - 2020-12-31 ### Other - Enable all stable features in the playground (#1081) diff --git a/Cargo.toml b/Cargo.toml index 512ed589ac9..69a68444c13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.8.1" +version = "0.8.2" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From 22dec87aac43e0e92eb15fa85b2de6da5b3c95b7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 23 Jan 2021 16:25:02 +0000 Subject: [PATCH 008/443] CI: more accurate no-default-feature and nightly test targets Also more accurate flags in examples --- .github/workflows/test.yml | 7 ++++--- examples/monte-carlo.rs | 2 +- examples/monty-hall.rs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 740cfc8b872..4c51fce7e17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,14 +73,15 @@ jobs: - name: Maybe nightly if: ${{ matrix.toolchain == 'nightly' }} run: | - cargo test --target ${{ matrix.target }} --tests --features=nightly + cargo test --target ${{ matrix.target }} --features=nightly cargo test --target ${{ matrix.target }} --all-features cargo test --target ${{ matrix.target }} --benches --features=nightly cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches - name: Test rand run: | - cargo test --target ${{ matrix.target }} --tests --no-default-features - cargo test --target ${{ matrix.target }} --tests --no-default-features --features=alloc,getrandom,small_rng + cargo test --target ${{ matrix.target }} --lib --tests --no-default-features + cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng + cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng # all stable features: cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng cargo test --target ${{ matrix.target }} --examples diff --git a/examples/monte-carlo.rs b/examples/monte-carlo.rs index 70560d0fab9..6cc9f4e142a 100644 --- a/examples/monte-carlo.rs +++ b/examples/monte-carlo.rs @@ -24,7 +24,7 @@ //! the square at random, calculate the fraction that fall within the circle, //! and multiply this fraction by 4. -#![cfg(feature = "std")] +#![cfg(all(feature = "std", feature = "std_rng"))] use rand::distributions::{Distribution, Uniform}; diff --git a/examples/monty-hall.rs b/examples/monty-hall.rs index 30e2f44d154..2a3b63d8df3 100644 --- a/examples/monty-hall.rs +++ b/examples/monty-hall.rs @@ -26,7 +26,7 @@ //! //! [Monty Hall Problem]: https://en.wikipedia.org/wiki/Monty_Hall_problem -#![cfg(feature = "std")] +#![cfg(all(feature = "std", feature = "std_rng"))] use rand::distributions::{Distribution, Uniform}; use rand::Rng; From fa615efd91f83fe8e56799cfa9217d67f7625fb7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 23 Jan 2021 16:43:45 +0000 Subject: [PATCH 009/443] Feature gate choose_multiple_weighted on std --- src/seq/index.rs | 11 +++++++---- src/seq/mod.rs | 12 ++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/seq/index.rs b/src/seq/index.rs index c09e5804229..8b155e1f1e1 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -17,7 +17,9 @@ use alloc::collections::BTreeSet; #[cfg(feature = "std")] use std::collections::HashSet; #[cfg(feature = "alloc")] -use crate::distributions::{uniform::SampleUniform, Distribution, Uniform, WeightedError}; +use crate::distributions::{uniform::SampleUniform, Distribution, Uniform}; +#[cfg(feature = "std")] +use crate::distributions::WeightedError; use crate::Rng; #[cfg(feature = "serde1")] @@ -270,6 +272,8 @@ where R: Rng + ?Sized { /// `O(length + amount * log length)` time otherwise. /// /// Panics if `amount > length`. +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn sample_weighted( rng: &mut R, length: usize, weight: F, amount: usize, ) -> Result @@ -301,6 +305,7 @@ where /// + amount * log length)` time otherwise. /// /// Panics if `amount > length`. +#[cfg(feature = "std")] fn sample_efraimidis_spirakis( rng: &mut R, length: N, weight: F, amount: N, ) -> Result @@ -375,9 +380,6 @@ where #[cfg(not(feature = "nightly"))] { - #[cfg(all(feature = "alloc", not(feature = "std")))] - use crate::alloc::collections::BinaryHeap; - #[cfg(feature = "std")] use std::collections::BinaryHeap; // Partially sort the array such that the `amount` elements with the largest @@ -619,6 +621,7 @@ mod test { assert_eq!(v1, v2); } + #[cfg(feature = "std")] #[test] fn test_sample_weighted() { let seed_rng = crate::test::rng; diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 9e6ffaf1242..4375fa1ccc6 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -212,7 +212,11 @@ pub trait SliceRandom { /// println!("{:?}", choices.choose_multiple_weighted(&mut rng, 2, |item| item.1).unwrap().collect::>()); /// ``` /// [`choose_multiple`]: SliceRandom::choose_multiple - #[cfg(feature = "alloc")] + // + // Note: this is feature-gated on std due to usage of f64::powf. + // If necessary, we may use alloc+libm as an alternative (see PR #1089). + #[cfg(feature = "std")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] fn choose_multiple_weighted( &self, rng: &mut R, amount: usize, weight: F, ) -> Result, WeightedError> @@ -556,7 +560,7 @@ impl SliceRandom for [T] { Ok(&mut self[distr.sample(rng)]) } - #[cfg(feature = "alloc")] + #[cfg(feature = "std")] fn choose_multiple_weighted( &self, rng: &mut R, amount: usize, weight: F, ) -> Result, WeightedError> @@ -1228,7 +1232,7 @@ mod test { } #[test] - #[cfg(feature = "alloc")] + #[cfg(feature = "std")] fn test_multiple_weighted_edge_cases() { use super::*; @@ -1308,7 +1312,7 @@ mod test { } #[test] - #[cfg(feature = "alloc")] + #[cfg(feature = "std")] fn test_multiple_weighted_distributions() { use super::*; From 8821743325303e4deb6c4a6c934f0e2eb9680205 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 23 Jan 2021 16:46:53 +0000 Subject: [PATCH 010/443] Prepare 0.8.3 --- CHANGELOG.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0495305fd7..536c510af81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [0.8.3] - 2021-01-25 +### Fixes +- Fix `no-std` + `alloc` build by gating `choose_multiple_weighted` on `std` (#1088) + ## [0.8.2] - 2021-01-12 ### Fixes - Fix panic in `UniformInt::sample_single_inclusive` and `Rng::gen_range` when diff --git a/Cargo.toml b/Cargo.toml index 69a68444c13..afd13391d9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.8.2" +version = "0.8.3" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From 91f53708ef5efaba5835b47b2008ba98b79cef11 Mon Sep 17 00:00:00 2001 From: Andrew Wagner Date: Sat, 6 Feb 2021 03:00:35 +0100 Subject: [PATCH 011/443] Reorder asserts for easier debugging. When the caller erroneously passes NaN's, it is much easier to diagnose with the asserts in this order. With finite arguments, the error message is unchanged. --- src/distributions/uniform.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index e4a6407fd66..66ba5bdb58e 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -827,11 +827,15 @@ macro_rules! uniform_float_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.all_lt(high), "Uniform::new called with `low >= high`"); assert!( - low.all_finite() && high.all_finite(), - "Uniform::new called with non-finite boundaries" + low.all_finite() + "Uniform::new called with `low` non-finite." ); + assert!( + high.all_finite() + "Uniform::new called with `high` non-finite." + ); + assert!(low.all_lt(high), "Uniform::new called with `low >= high`"); let max_rand = <$ty>::splat( (::core::$u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, ); From cdb32807c93ced94f15b17df60e823b32379ac07 Mon Sep 17 00:00:00 2001 From: Andrew Wagner Date: Sat, 6 Feb 2021 03:07:09 +0100 Subject: [PATCH 012/443] Update uniform.rs --- src/distributions/uniform.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 66ba5bdb58e..e8d0dcad8b3 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -828,11 +828,11 @@ macro_rules! uniform_float_impl { let low = *low_b.borrow(); let high = *high_b.borrow(); assert!( - low.all_finite() + low.all_finite(), "Uniform::new called with `low` non-finite." ); assert!( - high.all_finite() + high.all_finite(), "Uniform::new called with `high` non-finite." ); assert!(low.all_lt(high), "Uniform::new called with `low >= high`"); From 390a7b1049fa5ba1d627feaef2a1629e0e7826b4 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Wed, 10 Feb 2021 18:55:08 -0800 Subject: [PATCH 013/443] Fix assertions inside read_{u32,u64}_into Unless I'm mistaken the size multiplier was on the wrong side, making the assertions too permissive. This could have resulted in destination buffers not being fully populated. --- rand_core/src/le.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_core/src/le.rs b/rand_core/src/le.rs index fa338928403..ed42e57f478 100644 --- a/rand_core/src/le.rs +++ b/rand_core/src/le.rs @@ -16,7 +16,7 @@ use core::convert::TryInto; /// Reads unsigned 32 bit integers from `src` into `dst`. #[inline] pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { - assert!(4 * src.len() >= dst.len()); + assert!(src.len() >= 4 * dst.len()); for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(4)) { *out = u32::from_le_bytes(chunk.try_into().unwrap()); } @@ -25,7 +25,7 @@ pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { /// Reads unsigned 64 bit integers from `src` into `dst`. #[inline] pub fn read_u64_into(src: &[u8], dst: &mut [u64]) { - assert!(8 * src.len() >= dst.len()); + assert!(src.len() >= 8 * dst.len()); for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(8)) { *out = u64::from_le_bytes(chunk.try_into().unwrap()); } From 2cf5120dd5642f6bdb8d5359ddc6bb459489a2d9 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Fri, 12 Feb 2021 17:45:28 -0800 Subject: [PATCH 014/443] Bump to 0.6.2 --- rand_core/CHANGELOG.md | 5 +++++ rand_core/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index c0c2418f9b2..23d1fa5a1d3 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.2] - 2021-02-12 +### Fixed +- Fixed assertions in `le::read_u32_into` and `le::read_u64_into` which could + have allowed buffers not to be fully populated (#1096) + ## [0.6.1] - 2021-01-03 ### Fixed - Avoid panic when using `RngCore::seed_from_u64` with a seed which is not a diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index bfb877dd7ea..d460757d21d 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.6.1" +version = "0.6.2" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From 1b0ed1dea5d3ed28a3c3d188bc3fccbe668c8efb Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 5 Mar 2021 03:33:11 +0100 Subject: [PATCH 015/443] Correctly document `rand_distr` features Previously, the features were undocumented and it was incorrectly claimed that `no_std` is not supported. --- rand_distr/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rand_distr/README.md b/rand_distr/README.md index 29b5fe0853e..c10c3e533d7 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -19,7 +19,12 @@ It is worth mentioning the [statrs] crate which provides similar functionality along with various support functions, including PDF and CDF computation. In contrast, this `rand_distr` crate focusses on sampling from distributions. -Unlike most Rand crates, `rand_distr` does not currently support `no_std`. +If the `std` default feature is enabled, `rand_distr` uses the floating point +functions from `std`. Otherwise, the floating point functions from `num_traits` +and `libm` are used to support `no_std` environments. + +The default `alloc` feature (which is implied by the `std` feature) is required +for some distributions (in particular, `Dirichlet` and `WeightedAliasIndex`). Links: From 4b1d97451bbdbc9c5d86063ac6f9f56f53af77c9 Mon Sep 17 00:00:00 2001 From: Noam Koren Date: Sat, 13 Mar 2021 18:45:59 +0200 Subject: [PATCH 016/443] Use const-generics to support arrays of all sizes --- src/distributions/other.rs | 19 +++++++++++++++++++ src/rng.rs | 12 ++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index f62fe59aa73..ab4e4d84c97 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -156,6 +156,24 @@ tuple_impl! {A, B, C, D, E, F, G, H, I, J} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K, L} +#[cfg(feature = "nightly")] +impl Distribution<[T; N]> for Standard +where + Standard: Distribution, + T: Default + Copy, +{ + #[inline] + fn sample(&self, _rng: &mut R) -> [T; N] { + let mut sample = [Default::default(); N]; + for elem in &mut sample { + *elem = _rng.gen(); + } + + sample + } +} + +#[cfg(not(feature = "nightly"))] macro_rules! array_impl { // recursive, given at least one type parameter: {$n:expr, $t:ident, $($ts:ident,)*} => { @@ -176,6 +194,7 @@ macro_rules! array_impl { }; } +#[cfg(not(feature = "nightly"))] array_impl! {32, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,} impl Distribution> for Standard diff --git a/src/rng.rs b/src/rng.rs index bb977a54379..213b4ff8760 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -394,6 +394,16 @@ impl_fill!(i8, i16, i32, i64, isize,); #[cfg(not(target_os = "emscripten"))] impl_fill!(i128); +#[cfg(feature = "nightly")] +impl Fill for [T; N] +where [T]: Fill +{ + fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { + self[..].try_fill(rng) + } +} + +#[cfg(not(feature = "nightly"))] macro_rules! impl_fill_arrays { ($n:expr,) => {}; ($n:expr, $N:ident) => { @@ -413,8 +423,10 @@ macro_rules! impl_fill_arrays { impl_fill_arrays!(!div $n / 2, $($NN,)*); }; } +#[cfg(not(feature = "nightly"))] #[rustfmt::skip] impl_fill_arrays!(32, N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,); +#[cfg(not(feature = "nightly"))] impl_fill_arrays!(!div 4096, N,N,N,N,N,N,N,); #[cfg(test)] From 64ad4e0e6f2b8d11c015e416dc7568061d6d79da Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 13 Mar 2021 16:38:42 -0300 Subject: [PATCH 017/443] rand_distr: Forward "std" feature to "num-traits" This means that the floating point functions from `std` are used if available, instead of always using the ones from `libm`. --- rand_distr/Cargo.toml | 2 +- rand_distr/README.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 027d7753adf..7d8b3697127 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -21,7 +21,7 @@ num-traits = { version = "0.2", default-features = false, features = ["libm"] } [features] default = ["std"] -std = ["alloc", "rand/std"] +std = ["alloc", "rand/std", "num-traits/std"] alloc = ["rand/alloc"] [dev-dependencies] diff --git a/rand_distr/README.md b/rand_distr/README.md index c10c3e533d7..a60a98dfd0f 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -21,7 +21,8 @@ contrast, this `rand_distr` crate focusses on sampling from distributions. If the `std` default feature is enabled, `rand_distr` uses the floating point functions from `std`. Otherwise, the floating point functions from `num_traits` -and `libm` are used to support `no_std` environments. +and `libm` are used to support `no_std` environments. Note that this may affect +the value stability of the distributions. The default `alloc` feature (which is implied by the `std` feature) is required for some distributions (in particular, `Dirichlet` and `WeightedAliasIndex`). From 9a5a12f32307d4e89ae66d583e4553bdd1693cf4 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 13 Mar 2021 18:46:15 -0300 Subject: [PATCH 018/443] Improve feature documentation on docs.rs --- rand_distr/src/binomial.rs | 1 + rand_distr/src/cauchy.rs | 1 + rand_distr/src/dirichlet.rs | 1 + rand_distr/src/exponential.rs | 1 + rand_distr/src/gamma.rs | 4 ++++ rand_distr/src/geometric.rs | 1 + rand_distr/src/lib.rs | 2 ++ rand_distr/src/normal.rs | 1 + rand_distr/src/pareto.rs | 1 + rand_distr/src/pert.rs | 1 + rand_distr/src/poisson.rs | 1 + rand_distr/src/triangular.rs | 1 + rand_distr/src/weibull.rs | 1 + rand_distr/src/weighted_alias.rs | 2 ++ 14 files changed, 19 insertions(+) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 476ae64f559..f4fbbfabaa3 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -55,6 +55,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Binomial { diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 952b7f29dc9..a68e12f7796 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -55,6 +55,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Cauchy diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 391e428d351..6286e935742 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -68,6 +68,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Dirichlet diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index fb9818974ad..e389319d91f 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -114,6 +114,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Exp diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 5e98dbdfcfc..0d2d87db9f0 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -81,6 +81,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} #[derive(Clone, Copy, Debug)] @@ -298,6 +299,7 @@ impl fmt::Display for ChiSquaredError { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for ChiSquaredError {} #[derive(Clone, Copy, Debug)] @@ -404,6 +406,7 @@ impl fmt::Display for FisherFError { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for FisherFError {} impl FisherF @@ -567,6 +570,7 @@ impl fmt::Display for BetaError { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for BetaError {} impl Beta diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index c51168af0c2..8cb10576e6a 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -49,6 +49,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Geometric { diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 0043bd9f62e..f44292724ab 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -94,6 +94,7 @@ pub use rand::distributions::{ pub use self::binomial::{Binomial, Error as BinomialError}; pub use self::cauchy::{Cauchy, Error as CauchyError}; #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use self::dirichlet::{Dirichlet, Error as DirichletError}; pub use self::exponential::{Error as ExpError, Exp, Exp1}; pub use self::gamma::{ @@ -118,6 +119,7 @@ pub use self::weibull::{Error as WeibullError, Weibull}; #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use rand::distributions::{WeightedError, WeightedIndex}; #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use weighted_alias::WeightedAliasIndex; pub use num_traits; diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 8c3c8f8fd2f..a34e8e405ce 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -138,6 +138,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Normal diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index 3250c86ffe9..53e2987fce1 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -50,6 +50,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Pareto diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index d6905e014bf..e53ea0b89e8 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -65,6 +65,7 @@ impl fmt::Display for PertError { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for PertError {} impl Pert diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index a190256e15e..dc21ef3e7ce 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -56,6 +56,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Poisson diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index 6d3d4cfd03f..97693b32fcc 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -61,6 +61,7 @@ impl fmt::Display for TriangularError { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for TriangularError {} impl Triangular diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index 184e5e06b16..aa9bdc44405 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -50,6 +50,7 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Weibull diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 527aece7479..1b07229be8c 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -354,6 +354,7 @@ impl_weight_for_float!(f64); impl_weight_for_float!(f32); impl_weight_for_int!(usize); #[cfg(not(target_os = "emscripten"))] +#[cfg_attr(doc_cfg, doc(cfg(not(target_os = "emscripten"))))] impl_weight_for_int!(u128); impl_weight_for_int!(u64); impl_weight_for_int!(u32); @@ -361,6 +362,7 @@ impl_weight_for_int!(u16); impl_weight_for_int!(u8); impl_weight_for_int!(isize); #[cfg(not(target_os = "emscripten"))] +#[cfg_attr(doc_cfg, doc(cfg(not(target_os = "emscripten"))))] impl_weight_for_int!(i128); impl_weight_for_int!(i64); impl_weight_for_int!(i32); From 7c0042d637bcdcc0a29baacae09d1780eaf76a14 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 13 Mar 2021 19:07:59 -0300 Subject: [PATCH 019/443] Add `rand_distr` to "no-std" category --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 7d8b3697127..a313971f679 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -11,7 +11,7 @@ description = """ Sampling from random number distributions """ keywords = ["random", "rng", "distribution", "probability"] -categories = ["algorithms"] +categories = ["algorithms", "no-std"] edition = "2018" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] From 65b170759b2bd1ab496ffa26c2e25fedbbcb3fe8 Mon Sep 17 00:00:00 2001 From: Noam Koren Date: Mon, 15 Mar 2021 00:07:31 +0200 Subject: [PATCH 020/443] Add min_const_gen feature --- Cargo.toml | 3 +++ src/distributions/other.rs | 6 +++--- src/rng.rs | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index afd13391d9d..3b6592dffdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,9 @@ std_rng = ["rand_chacha", "rand_hc"] # Option: enable SmallRng small_rng = [] +# Option: TODO - enable SmallRng +min_const_gen = [] + [workspace] members = [ "rand_core", diff --git a/src/distributions/other.rs b/src/distributions/other.rs index ab4e4d84c97..55d0c90987f 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -156,7 +156,7 @@ tuple_impl! {A, B, C, D, E, F, G, H, I, J} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K, L} -#[cfg(feature = "nightly")] +#[cfg(feature = "min_const_gen")] impl Distribution<[T; N]> for Standard where Standard: Distribution, @@ -173,7 +173,7 @@ where } } -#[cfg(not(feature = "nightly"))] +#[cfg(not(feature = "min_const_gen"))] macro_rules! array_impl { // recursive, given at least one type parameter: {$n:expr, $t:ident, $($ts:ident,)*} => { @@ -194,7 +194,7 @@ macro_rules! array_impl { }; } -#[cfg(not(feature = "nightly"))] +#[cfg(not(feature = "min_const_gen"))] array_impl! {32, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,} impl Distribution> for Standard diff --git a/src/rng.rs b/src/rng.rs index 213b4ff8760..7bbaa49483b 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -394,7 +394,7 @@ impl_fill!(i8, i16, i32, i64, isize,); #[cfg(not(target_os = "emscripten"))] impl_fill!(i128); -#[cfg(feature = "nightly")] +#[cfg(feature = "min_const_gen")] impl Fill for [T; N] where [T]: Fill { @@ -403,7 +403,7 @@ where [T]: Fill } } -#[cfg(not(feature = "nightly"))] +#[cfg(not(feature = "min_const_gen"))] macro_rules! impl_fill_arrays { ($n:expr,) => {}; ($n:expr, $N:ident) => { @@ -423,10 +423,10 @@ macro_rules! impl_fill_arrays { impl_fill_arrays!(!div $n / 2, $($NN,)*); }; } -#[cfg(not(feature = "nightly"))] +#[cfg(not(feature = "min_const_gen"))] #[rustfmt::skip] impl_fill_arrays!(32, N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,); -#[cfg(not(feature = "nightly"))] +#[cfg(not(feature = "min_const_gen"))] impl_fill_arrays!(!div 4096, N,N,N,N,N,N,N,); #[cfg(test)] From ffb6f360ad3612c35e62731b16ed65df223a17eb Mon Sep 17 00:00:00 2001 From: Noam Koren Date: Mon, 15 Mar 2021 00:20:15 +0200 Subject: [PATCH 021/443] Use MaybeUninit to initialize a random array, remove Default + Copy trait bounds --- src/distributions/other.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 55d0c90987f..16b352ac382 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -16,6 +16,9 @@ use crate::Rng; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; +#[cfg(feature = "min_const_gen")] +use std::mem::{self, MaybeUninit}; + // ----- Sampling distributions ----- @@ -158,18 +161,17 @@ tuple_impl! {A, B, C, D, E, F, G, H, I, J, K, L} #[cfg(feature = "min_const_gen")] impl Distribution<[T; N]> for Standard -where - Standard: Distribution, - T: Default + Copy, +where Standard: Distribution { #[inline] fn sample(&self, _rng: &mut R) -> [T; N] { - let mut sample = [Default::default(); N]; - for elem in &mut sample { - *elem = _rng.gen(); + let mut buff: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + for elem in &mut buff { + *elem = MaybeUninit::new(_rng.gen()); } - sample + unsafe { mem::transmute_copy::<_, _>(&buff) } } } From cfbd0c6d5000ac44a9c33cae5eea324c73e2be86 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sat, 20 Mar 2021 01:26:52 -0400 Subject: [PATCH 022/443] Tiny docs update --- src/rng.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rng.rs b/src/rng.rs index bb977a54379..abb6750d26d 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -28,7 +28,7 @@ use core::{mem, slice}; /// - Since `Rng: RngCore` and every `RngCore` implements `Rng`, it makes no /// difference whether we use `R: Rng` or `R: RngCore`. /// - The `+ ?Sized` un-bounding allows functions to be called directly on -/// type-erased references; i.e. `foo(r)` where `r: &mut RngCore`. Without +/// type-erased references; i.e. `foo(r)` where `r: &mut dyn RngCore`. Without /// this it would be necessary to write `foo(&mut r)`. /// /// An alternative pattern is possible: `fn foo(rng: R)`. This has some From 773ba59c9afff59588ca54cc3651196a2ebaee19 Mon Sep 17 00:00:00 2001 From: Noam Koren Date: Sat, 20 Mar 2021 11:43:57 +0200 Subject: [PATCH 023/443] Update docs --- Cargo.toml | 2 +- src/distributions/mod.rs | 2 ++ src/rng.rs | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3b6592dffdd..33477f77c92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ std_rng = ["rand_chacha", "rand_hc"] # Option: enable SmallRng small_rng = [] -# Option: TODO - enable SmallRng +# Option: enable generating random arrays of any size using min-const-generics min_const_gen = [] [workspace] diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 652f52a1831..8c0232714df 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -279,6 +279,8 @@ where /// * Arrays (up to 32 elements): each element is generated sequentially; /// see also [`Rng::fill`] which supports arbitrary array length for integer /// types and tends to be faster for `u32` and smaller types. +/// For arrays of size larger than 32 elements, enable the `min_const_gen` +/// feature. /// * `Option` first generates a `bool`, and if true generates and returns /// `Some(value)` where `value: T`, otherwise returning `None`. /// diff --git a/src/rng.rs b/src/rng.rs index 7bbaa49483b..6d99fc97cf4 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -71,6 +71,7 @@ pub trait Rng: RngCore { /// The `rng.gen()` method is able to generate arrays (up to 32 elements) /// and tuples (up to 12 elements), so long as all element types can be /// generated. + /// For arrays larger than 32 elements, enable the `min_const_gen` feature. /// /// For arrays of integers, especially for those with small element types /// (< 64 bit), it will likely be faster to instead use [`Rng::fill`]. From b58e6598a85ab2fa7582bbad2cd5eaf6abb2f59c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 20 Mar 2021 14:25:15 -0300 Subject: [PATCH 024/443] Revert "rand_distr: Forward "std" feature to "num-traits"" This reverts commit 64ad4e0e6f2b8d11c015e416dc7568061d6d79da. --- rand_distr/Cargo.toml | 2 +- rand_distr/README.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index a313971f679..9dcf1ed3ccf 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -21,7 +21,7 @@ num-traits = { version = "0.2", default-features = false, features = ["libm"] } [features] default = ["std"] -std = ["alloc", "rand/std", "num-traits/std"] +std = ["alloc", "rand/std"] alloc = ["rand/alloc"] [dev-dependencies] diff --git a/rand_distr/README.md b/rand_distr/README.md index a60a98dfd0f..c10c3e533d7 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -21,8 +21,7 @@ contrast, this `rand_distr` crate focusses on sampling from distributions. If the `std` default feature is enabled, `rand_distr` uses the floating point functions from `std`. Otherwise, the floating point functions from `num_traits` -and `libm` are used to support `no_std` environments. Note that this may affect -the value stability of the distributions. +and `libm` are used to support `no_std` environments. The default `alloc` feature (which is implied by the `std` feature) is required for some distributions (in particular, `Dirichlet` and `WeightedAliasIndex`). From 382e3d4d4640c64f05372d534ccbef9e717de9b5 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 20 Mar 2021 14:31:27 -0300 Subject: [PATCH 025/443] Update README to mention that we always use `libm` over `std` --- rand_distr/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rand_distr/README.md b/rand_distr/README.md index c10c3e533d7..a06db2a2aff 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -19,13 +19,15 @@ It is worth mentioning the [statrs] crate which provides similar functionality along with various support functions, including PDF and CDF computation. In contrast, this `rand_distr` crate focusses on sampling from distributions. -If the `std` default feature is enabled, `rand_distr` uses the floating point -functions from `std`. Otherwise, the floating point functions from `num_traits` -and `libm` are used to support `no_std` environments. +If the `std` default feature is enabled, `rand_distr` implements the `Error` +trait for its error types. The default `alloc` feature (which is implied by the `std` feature) is required for some distributions (in particular, `Dirichlet` and `WeightedAliasIndex`). +The floating point functions from `num_traits` and `libm` are used to support +`no_std` environments and ensure reproducibility. + Links: - [API documentation (master)](https://rust-random.github.io/rand/rand_distr) From 299385485dbd4bbac4b53b3a7bd2a05dd7443354 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sun, 21 Mar 2021 01:17:06 -0400 Subject: [PATCH 026/443] Initial implementation of Slice distribution --- src/distributions/mod.rs | 18 +++++--- src/distributions/slice.rs | 95 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/distributions/slice.rs diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 652f52a1831..e4830997256 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -99,7 +99,9 @@ use core::iter; pub use self::bernoulli::{Bernoulli, BernoulliError}; pub use self::float::{Open01, OpenClosed01}; pub use self::other::Alphanumeric; -#[doc(inline)] pub use self::uniform::Uniform; +pub use self::slice::Slice; +#[doc(inline)] +pub use self::uniform::Uniform; #[cfg(feature = "alloc")] pub use self::weighted_index::{WeightedError, WeightedIndex}; @@ -107,14 +109,18 @@ pub use self::weighted_index::{WeightedError, WeightedIndex}; mod bernoulli; pub mod uniform; -#[deprecated(since = "0.8.0", note = "use rand::distributions::{WeightedIndex, WeightedError} instead")] +#[deprecated( + since = "0.8.0", + note = "use rand::distributions::{WeightedIndex, WeightedError} instead" +)] #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod weighted; -#[cfg(feature = "alloc")] mod weighted_index; +#[cfg(feature = "alloc")] +mod weighted_index; #[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; mod float; #[doc(hidden)] @@ -123,6 +129,7 @@ pub mod hidden_export { } mod integer; mod other; +mod slice; mod utils; /// Types (distributions) that can be used to create a random instance of `T`. @@ -200,7 +207,6 @@ impl<'a, T, D: Distribution> Distribution for &'a D { } } - /// An iterator that generates random values of `T` with distribution `D`, /// using `R` as the source of randomness. /// @@ -250,7 +256,6 @@ where { } - /// A generic random value distribution, implemented for many primitive types. /// Usually generates values with a numerically uniform distribution, and with a /// range appropriate to the type. @@ -331,7 +336,6 @@ where #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Standard; - #[cfg(test)] mod tests { use super::{Distribution, Uniform}; diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs new file mode 100644 index 00000000000..3c2a9731610 --- /dev/null +++ b/src/distributions/slice.rs @@ -0,0 +1,95 @@ +// Copyright 2021 Developers of the Rand project. + +use crate::distributions::{Distribution, Uniform}; + +/// A distribution to sample items uniformly from a slice. +/// +/// [`Slice::new`] constructs a distribution referencing a slice and uniformly +/// samples references from the items in the slice. It may do extra work up +/// front to make sampling of multiple values faster; if only one sample from +/// the slice is required, [`SliceRandom::choose`] can be more efficient. +/// +/// Steps are taken to avoid bias which might be present in naive +/// implementations; for example `slice[rng.gen() % slice.len()]` samples from +/// the slice, but may be more likely to select numbers in the low range than +/// other values. +/// +/// This distribution samples with replacement; each sample is independent. +/// Sampling without replacement requires state to be retained, and therefore +/// cannot be handled by a distribution; you should instead consider methods +/// on [`SliceRandom`], such as [`SliceRandom::choose_multiple`]. +/// +/// # Example +/// +/// ``` +/// use rand::distributions::Slice; +/// +/// let vowels = ['a', 'e', 'i', 'o', 'u']; +/// let vowels = Slice::new(vowels).unwrap(); +/// let mut rng = rand::thread_rng(); +/// +/// // build a string of 10 vowels +/// let vowel_string: String = vowels +/// .sample_iter(&mut rng) +/// .take(10) +/// .collect(); +/// +/// println!("{}", vowel_string); +/// assert_eq!(vowel_string.len(), 10); +/// assert!(vowel_string().chars().all(|c| vowels.contains(c))); +/// ``` +/// +/// For a single sample, [`SliceRandom::choose`][crate::seq::SliceRandom::choose] +/// may be preferred: +/// +/// ``` +/// use rand::Rng; +/// use rand::seq::SliceRandom; +/// +/// let vowels = ['a', 'e', 'i', 'o', 'u']; +/// let mut rng = rand::thread_rng(); +/// +/// println!("{}", vowels.choose(&mut rng)) +/// ``` +/// +/// [`SliceRandom`]: crate::seq::SliceRandom +/// [`SliceRandom::choose`]: crate::seq::SliceRandom::choose +/// [`SliceRandom::choose_multiple`]: crate::seq::SliceRandom::choose_multiple +#[derive(Debug)] +pub struct Slice<'a, T> { + slice: &'a [T], + range: Uniform, +} + +impl<'a, T> Slice<'a, T> { + /// Create a new `Slice` instance which samples uniformly from the slice. + /// Returns `None` if the slice is empty. + pub fn new(slice: &'a [T]) -> Option { + match slice.len() { + 0 => None, + len => Some(Self { + slice, + range: Uniform::new(0, len), + }), + } + } +} + +impl Clone for Slice<'_, T> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Slice<'_, T> {} + +impl<'a, T> Distribution<&'a T> for Slice<'a, T> { + fn sample(&self, rng: &mut R) -> &'a T { + let idx = self.range.sample(rng); + + // Safety: at construction time, it was ensured that the slice was + // non-empty, and that the `Uniform` range produces values in range + // for the slice + unsafe { self.slice.get_unchecked(idx) } + } +} From 399c8cc564b157a9f1519450ae40b4a5810bad0b Mon Sep 17 00:00:00 2001 From: Noam Koren Date: Tue, 23 Mar 2021 15:52:08 +0200 Subject: [PATCH 027/443] Update docs --- src/distributions/mod.rs | 4 ++-- src/rng.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 8c0232714df..16a6e2412d6 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -279,8 +279,8 @@ where /// * Arrays (up to 32 elements): each element is generated sequentially; /// see also [`Rng::fill`] which supports arbitrary array length for integer /// types and tends to be faster for `u32` and smaller types. -/// For arrays of size larger than 32 elements, enable the `min_const_gen` -/// feature. +/// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support +/// arrays larger than 32 elements. /// * `Option` first generates a `bool`, and if true generates and returns /// `Some(value)` where `value: T`, otherwise returning `None`. /// diff --git a/src/rng.rs b/src/rng.rs index 6d99fc97cf4..8bffa7b6e0e 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -71,7 +71,8 @@ pub trait Rng: RngCore { /// The `rng.gen()` method is able to generate arrays (up to 32 elements) /// and tuples (up to 12 elements), so long as all element types can be /// generated. - /// For arrays larger than 32 elements, enable the `min_const_gen` feature. + /// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support + /// arrays larger than 32 elements. /// /// For arrays of integers, especially for those with small element types /// (< 64 bit), it will likely be faster to instead use [`Rng::fill`]. From 5161f70fb7143e72870abbb1072de763d714d1de Mon Sep 17 00:00:00 2001 From: Noam Koren Date: Tue, 23 Mar 2021 23:51:00 +0200 Subject: [PATCH 028/443] Update docs --- Cargo.toml | 3 ++- src/distributions/mod.rs | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 33477f77c92..a1bfb5b5efa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,8 @@ std_rng = ["rand_chacha", "rand_hc"] # Option: enable SmallRng small_rng = [] -# Option: enable generating random arrays of any size using min-const-generics +# Option: for rustc ≥ 1.51, enable generating random arrays of any size +# using min-const-generics min_const_gen = [] [workspace] diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 16a6e2412d6..911d1604aae 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -281,6 +281,10 @@ where /// types and tends to be faster for `u32` and smaller types. /// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support /// arrays larger than 32 elements. +/// Note that [`Rng::fill`] and `Standard`'s array support are *not* equivalent: +/// the former is optimised for integer types (using fewer RNG calls for +/// element types smaller than the RNG word size), while the latter supports +/// any element type supported by `Standard`. /// * `Option` first generates a `bool`, and if true generates and returns /// `Some(value)` where `value: T`, otherwise returning `None`. /// From df40da90539f3ddcc301d5c38bb1f17cea9d7089 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 27 Mar 2021 00:19:45 -0300 Subject: [PATCH 029/443] rand_distr: Add "std_math" feature --- rand_distr/Cargo.toml | 1 + rand_distr/README.md | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 9dcf1ed3ccf..a3072b00310 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -23,6 +23,7 @@ num-traits = { version = "0.2", default-features = false, features = ["libm"] } default = ["std"] std = ["alloc", "rand/std"] alloc = ["rand/alloc"] +std_math = ["num-traits/std"] [dev-dependencies] rand_pcg = { version = "0.3.0", path = "../rand_pcg" } diff --git a/rand_distr/README.md b/rand_distr/README.md index a06db2a2aff..90cffbb177d 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -26,7 +26,10 @@ The default `alloc` feature (which is implied by the `std` feature) is required for some distributions (in particular, `Dirichlet` and `WeightedAliasIndex`). The floating point functions from `num_traits` and `libm` are used to support -`no_std` environments and ensure reproducibility. +`no_std` environments and ensure reproducibility. If the floating point +functions from `std` are prefered, which may provide better accuracy and +performance but may produce different random values, the `std_math` feature +can be enabled. Links: From 52f9a8f4969c47ee9f49912101ce86ad385bb6c4 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 27 Mar 2021 00:24:00 -0300 Subject: [PATCH 030/443] rand_distr: Test all features --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4c51fce7e17..1d778d2d7ce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -91,7 +91,10 @@ jobs: cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features --features=alloc,getrandom - name: Test rand_distr - run: cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml + run: | + cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml + cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --no-default-features + cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --no-default-features --features=std,std_math - name: Test rand_pcg run: cargo test --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde1 - name: Test rand_chacha From 3961f9d6cdd0887791997e4daa6c6b4f2180d16f Mon Sep 17 00:00:00 2001 From: Nathan West Date: Sun, 28 Mar 2021 15:04:29 -0400 Subject: [PATCH 031/443] Fix doctests --- src/distributions/slice.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 3c2a9731610..9d562e332b7 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -22,34 +22,34 @@ use crate::distributions::{Distribution, Uniform}; /// # Example /// /// ``` +/// use rand::Rng; /// use rand::distributions::Slice; /// /// let vowels = ['a', 'e', 'i', 'o', 'u']; -/// let vowels = Slice::new(vowels).unwrap(); -/// let mut rng = rand::thread_rng(); +/// let vowels_dist = Slice::new(&vowels).unwrap(); +/// let rng = rand::thread_rng(); /// /// // build a string of 10 vowels -/// let vowel_string: String = vowels -/// .sample_iter(&mut rng) +/// let vowel_string: String = rng +/// .sample_iter(&vowels_dist) /// .take(10) /// .collect(); /// /// println!("{}", vowel_string); /// assert_eq!(vowel_string.len(), 10); -/// assert!(vowel_string().chars().all(|c| vowels.contains(c))); +/// assert!(vowel_string.chars().all(|c| vowels.contains(&c))); /// ``` /// /// For a single sample, [`SliceRandom::choose`][crate::seq::SliceRandom::choose] /// may be preferred: /// /// ``` -/// use rand::Rng; /// use rand::seq::SliceRandom; /// /// let vowels = ['a', 'e', 'i', 'o', 'u']; /// let mut rng = rand::thread_rng(); /// -/// println!("{}", vowels.choose(&mut rng)) +/// println!("{}", vowels.choose(&mut rng).unwrap()) /// ``` /// /// [`SliceRandom`]: crate::seq::SliceRandom From 825d0506948c540e1d712ec098e18acc2bf561e7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 29 Mar 2021 09:44:44 +0100 Subject: [PATCH 032/443] Uniform(float) distrs: add range overflow check and fix tests --- src/distributions/uniform.rs | 48 +++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index e8d0dcad8b3..576d761585a 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -827,11 +827,11 @@ macro_rules! uniform_float_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!( + debug_assert!( low.all_finite(), "Uniform::new called with `low` non-finite." ); - assert!( + debug_assert!( high.all_finite(), "Uniform::new called with `high` non-finite." ); @@ -841,6 +841,7 @@ macro_rules! uniform_float_impl { ); let mut scale = high - low; + assert!(scale.all_finite(), "Uniform::new: range overflow"); loop { let mask = (scale * max_rand + low).ge_mask(high); @@ -862,19 +863,24 @@ macro_rules! uniform_float_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); + debug_assert!( + low.all_finite(), + "Uniform::new_inclusive called with `low` non-finite." + ); + debug_assert!( + high.all_finite(), + "Uniform::new_inclusive called with `high` non-finite." + ); assert!( low.all_le(high), "Uniform::new_inclusive called with `low > high`" ); - assert!( - low.all_finite() && high.all_finite(), - "Uniform::new_inclusive called with non-finite boundaries" - ); let max_rand = <$ty>::splat( (::core::$u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, ); let mut scale = (high - low) / max_rand; + assert!(scale.all_finite(), "Uniform::new_inclusive: range overflow"); loop { let mask = (scale * max_rand + low).gt_mask(high); @@ -913,11 +919,20 @@ macro_rules! uniform_float_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); + debug_assert!( + low.all_finite(), + "UniformSampler::sample_single called with `low` non-finite." + ); + debug_assert!( + high.all_finite(), + "UniformSampler::sample_single called with `high` non-finite." + ); assert!( low.all_lt(high), "UniformSampler::sample_single: low >= high" ); let mut scale = high - low; + assert!(scale.all_finite(), "UniformSampler::sample_single: range overflow"); loop { // Generate a value in the range [1, 2) @@ -1332,12 +1347,8 @@ mod tests { (-<$f_scalar>::from_bits(10), -<$f_scalar>::from_bits(1)), (-<$f_scalar>::from_bits(5), 0.0), (-<$f_scalar>::from_bits(7), -0.0), - (10.0, ::core::$f_scalar::MAX), - (-100.0, ::core::$f_scalar::MAX), - (-::core::$f_scalar::MAX / 5.0, ::core::$f_scalar::MAX), - (-::core::$f_scalar::MAX, ::core::$f_scalar::MAX / 5.0), - (-::core::$f_scalar::MAX * 0.8, ::core::$f_scalar::MAX * 0.7), - (-::core::$f_scalar::MAX, ::core::$f_scalar::MAX), + (0.1 * ::core::$f_scalar::MAX, ::core::$f_scalar::MAX), + (-::core::$f_scalar::MAX * 0.2, ::core::$f_scalar::MAX * 0.7), ]; for &(low_scalar, high_scalar) in v.iter() { for lane in 0..<$ty>::lanes() { @@ -1416,6 +1427,19 @@ mod tests { } } + #[test] + #[should_panic] + fn test_float_overflow() { + Uniform::from(f64::MIN..f64::MAX); + } + + #[test] + #[should_panic] + fn test_float_overflow_single() { + let mut rng = crate::test::rng(252); + rng.gen_range(f64::MIN..f64::MAX); + } + #[test] #[cfg(all( feature = "std", From 7b1b2d00b21a3aa778fba9a3df110ae81aea4ecb Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 29 Mar 2021 09:48:30 +0100 Subject: [PATCH 033/443] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 536c510af81..377380bbbfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [Unreleased] +### Other +- Reorder asserts in `Uniform` float distributions for easier debugging of non-finite arguments + (#1094, #1108) +- Add range overflow check in `Uniform` float distributions (#1108) + ## [0.8.3] - 2021-01-25 ### Fixes - Fix `no-std` + `alloc` build by gating `choose_multiple_weighted` on `std` (#1088) From 80172a2071b48feb7ad4c42f3e79af7abe850992 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 29 Mar 2021 10:40:03 +0100 Subject: [PATCH 034/443] Changelog for #1104 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 377380bbbfc..d69d7948a83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. ## [Unreleased] +### Additions +- Use const-generics to support arrays of all sizes (#1104) + ### Other - Reorder asserts in `Uniform` float distributions for easier debugging of non-finite arguments (#1094, #1108) From b1cecac7c61f08386eaa37cff24721042016670d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 29 Mar 2021 10:41:31 +0100 Subject: [PATCH 035/443] Fix for MSRV --- src/distributions/uniform.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 576d761585a..57934838ae8 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1430,14 +1430,14 @@ mod tests { #[test] #[should_panic] fn test_float_overflow() { - Uniform::from(f64::MIN..f64::MAX); + Uniform::from(::core::f64::MIN..::core::f64::MAX); } #[test] #[should_panic] fn test_float_overflow_single() { let mut rng = crate::test::rng(252); - rng.gen_range(f64::MIN..f64::MAX); + rng.gen_range(::core::f64::MIN..::core::f64::MAX); } #[test] From 4deb2b834b86042df4da29cf0d507f5452582dcf Mon Sep 17 00:00:00 2001 From: Nathan West Date: Tue, 30 Mar 2021 22:09:12 -0400 Subject: [PATCH 036/443] Code review updates - Replace Option with Result - Add a debug_assert to the unsafe part of the code --- src/distributions/slice.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 9d562e332b7..a39e63c1c50 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -64,10 +64,10 @@ pub struct Slice<'a, T> { impl<'a, T> Slice<'a, T> { /// Create a new `Slice` instance which samples uniformly from the slice. /// Returns `None` if the slice is empty. - pub fn new(slice: &'a [T]) -> Option { + pub fn new(slice: &'a [T]) -> Result { match slice.len() { - 0 => None, - len => Some(Self { + 0 => Err(EmptySlice), + len => Ok(Self { slice, range: Uniform::new(0, len), }), @@ -87,9 +87,33 @@ impl<'a, T> Distribution<&'a T> for Slice<'a, T> { fn sample(&self, rng: &mut R) -> &'a T { let idx = self.range.sample(rng); + debug_assert!( + idx < self.slice.len(), + "Uniform::new(0, {}) somehow returned {}", + self.slice.len(), + idx + ); + // Safety: at construction time, it was ensured that the slice was // non-empty, and that the `Uniform` range produces values in range // for the slice unsafe { self.slice.get_unchecked(idx) } } } + +/// Error type indicating that a [`Slice`] distribution was improperly +/// constructed with an empty slice. +#[derive(Debug, Clone, Copy)] +pub struct EmptySlice; + +impl core::fmt::Display for EmptySlice { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "Tried to create a `distributions::Slice` with an empty slice" + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for EmptySlice {} From 91d7699a697eb303d689097c2733ae7d9f6af8cc Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Sat, 3 Apr 2021 19:06:32 -0700 Subject: [PATCH 037/443] Add an advance function to PCG generators Based on the C implementations to advance the generator. --- rand_pcg/src/pcg128.rs | 58 ++++++++++++++++++++++++++++++++++++++++++ rand_pcg/src/pcg64.rs | 29 +++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index 58a8e36b7c1..db006d22205 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -40,6 +40,35 @@ pub struct Lcg128Xsl64 { pub type Pcg64 = Lcg128Xsl64; impl Lcg128Xsl64 { + /// Multi-step advance functions (jump-ahead, jump-back) + /// + /// The method used here is based on Brown, "Random Number Generation + /// with Arbitrary Stride,", Transactions of the American Nuclear + /// Society (Nov. 1994). The algorithm is very similar to fast + /// exponentiation. + /// + /// Even though delta is an unsigned integer, we can pass a + /// signed integer to go backwards, it just goes "the long way round". + #[inline] + pub fn advance(&mut self, delta: u128) { + let mut acc_mult: u128 = 1; + let mut acc_plus: u128 = 0; + let mut cur_mult = MULTIPLIER; + let mut cur_plus = self.increment; + let mut mdelta = delta; + + while mdelta > 0 { + if (mdelta & 1) != 0 { + acc_mult = acc_mult.wrapping_mul(cur_mult); + acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus); + } + cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus); + cur_mult = cur_mult.wrapping_mul(cur_mult); + mdelta /= 2; + } + self.state = acc_mult.wrapping_mul(self.state).wrapping_add(acc_plus); + } + /// Construct an instance compatible with PCG seed and stream. /// /// Note that PCG specifies default values for both parameters: @@ -140,6 +169,35 @@ pub struct Mcg128Xsl64 { pub type Pcg64Mcg = Mcg128Xsl64; impl Mcg128Xsl64 { + /// Multi-step advance functions (jump-ahead, jump-back) + /// + /// The method used here is based on Brown, "Random Number Generation + /// with Arbitrary Stride,", Transactions of the American Nuclear + /// Society (Nov. 1994). The algorithm is very similar to fast + /// exponentiation. + /// + /// Even though delta is an unsigned integer, we can pass a + /// signed integer to go backwards, it just goes "the long way round". + #[inline] + pub fn advance(&mut self, delta: u128) { + let mut acc_mult: u128 = 1; + let mut acc_plus: u128 = 0; + let mut cur_mult = MULTIPLIER; + let mut cur_plus: u128 = 0; + let mut mdelta = delta; + + while mdelta > 0 { + if (mdelta & 1) != 0 { + acc_mult = acc_mult.wrapping_mul(cur_mult); + acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus); + } + cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus); + cur_mult = cur_mult.wrapping_mul(cur_mult); + mdelta /= 2; + } + self.state = acc_mult.wrapping_mul(self.state).wrapping_add(acc_plus); + } + /// Construct an instance compatible with PCG seed. /// /// Note that PCG specifies a default value for the parameter: diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index ed7442f9670..ec30ad2aacb 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -40,6 +40,35 @@ pub struct Lcg64Xsh32 { pub type Pcg32 = Lcg64Xsh32; impl Lcg64Xsh32 { + /// Multi-step advance functions (jump-ahead, jump-back) + /// + /// The method used here is based on Brown, "Random Number Generation + /// with Arbitrary Stride,", Transactions of the American Nuclear + /// Society (Nov. 1994). The algorithm is very similar to fast + /// exponentiation. + /// + /// Even though delta is an unsigned integer, we can pass a + /// signed integer to go backwards, it just goes "the long way round". + #[inline] + pub fn advance(&mut self, delta: u64) { + let mut acc_mult: u64 = 1; + let mut acc_plus: u64 = 0; + let mut cur_mult = MULTIPLIER; + let mut cur_plus = self.increment; + let mut mdelta = delta; + + while mdelta > 0 { + if (mdelta & 1) != 0 { + acc_mult = acc_mult.wrapping_mul(cur_mult); + acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus); + } + cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus); + cur_mult = cur_mult.wrapping_mul(cur_mult); + mdelta /= 2; + } + self.state = acc_mult.wrapping_mul(self.state).wrapping_add(acc_plus); + } + /// Construct an instance compatible with PCG seed and stream. /// /// Note that PCG specifies default values for both parameters: From d7be55c1fe2ca33df66507fb7a72b23e714162d4 Mon Sep 17 00:00:00 2001 From: ndebuhr Date: Mon, 5 Apr 2021 00:05:23 +0000 Subject: [PATCH 038/443] Fix rand_distr README.md typos --- rand_distr/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_distr/README.md b/rand_distr/README.md index 29b5fe0853e..4e949b4f031 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -2,7 +2,7 @@ [![Test Status](https://github.com/rust-random/rand/workflows/Tests/badge.svg?event=push)](https://github.com/rust-random/rand/actions) [![Latest version](https://img.shields.io/crates/v/rand_distr.svg)](https://crates.io/crates/rand_distr) -[[![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) +[![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) [![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) @@ -17,7 +17,7 @@ sphere surface. It is worth mentioning the [statrs] crate which provides similar functionality along with various support functions, including PDF and CDF computation. In -contrast, this `rand_distr` crate focusses on sampling from distributions. +contrast, this `rand_distr` crate focuses on sampling from distributions. Unlike most Rand crates, `rand_distr` does not currently support `no_std`. From 53d23aec59cface8b026a06ac256b8fe93d6f8b9 Mon Sep 17 00:00:00 2001 From: ndebuhr Date: Mon, 5 Apr 2021 00:16:45 +0000 Subject: [PATCH 039/443] Improve the rand_distr README.md by noting the new v0.2.0-v0.4.0 distributions and by making a couple of small language tweaks for clarity/consistency --- rand_distr/README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rand_distr/README.md b/rand_distr/README.md index 4e949b4f031..2cd4dbcc444 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -7,13 +7,14 @@ [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) [![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) -Implements a full suite of random number distributions sampling routines. - -This crate is a super-set of the [rand::distributions] module, including support -for sampling from Beta, Binomial, Cauchy, ChiSquared, Dirichlet, exponential, -Fisher F, Gamma, Log-normal, Normal, Pareto, Poisson, StudentT, Triangular and -Weibull distributions, as well as sampling points from the unit circle and unit -sphere surface. +Implements a full suite of random number distribution sampling routines. + +This crate is a superset of the [rand::distributions] module, including support +for sampling from Beta, Binomial, Cauchy, ChiSquared, Dirichlet, Exponential, +FisherF, Gamma, Geometric, Hypergeometric, InverseGaussian, LogNormal, Normal, +Pareto, PERT, Poisson, StudentT, Triangular and Weibull distributions. Sampling +from the unit ball, unit circle, unit disc and unit sphere surfaces is also +supported. It is worth mentioning the [statrs] crate which provides similar functionality along with various support functions, including PDF and CDF computation. In From a886beb64e891e01f10925ce4c0c15adf7ef9a64 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Tue, 6 Apr 2021 08:07:16 +0000 Subject: [PATCH 040/443] fix xoshiro256++ documentation --- src/rngs/xoshiro256plusplus.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rngs/xoshiro256plusplus.rs b/src/rngs/xoshiro256plusplus.rs index cd373c30669..8ffb18b8033 100644 --- a/src/rngs/xoshiro256plusplus.rs +++ b/src/rngs/xoshiro256plusplus.rs @@ -11,9 +11,9 @@ use rand_core::impls::fill_bytes_via_next; use rand_core::le::read_u64_into; use rand_core::{SeedableRng, RngCore, Error}; -/// A xoshiro256** random number generator. +/// A xoshiro256++ random number generator. /// -/// The xoshiro256** algorithm is not suitable for cryptographic purposes, but +/// The xoshiro256++ algorithm is not suitable for cryptographic purposes, but /// is very fast and has excellent statistical properties. /// /// The algorithm used here is translated from [the `xoshiro256plusplus.c` From 4bb723ee2f79800a46653c858b3ec28fd4fa009f Mon Sep 17 00:00:00 2001 From: Jim Turner Date: Thu, 8 Apr 2021 19:28:47 -0400 Subject: [PATCH 041/443] Add mean and std_dev accessors to Normal --- rand_distr/src/normal.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index a34e8e405ce..c3e99010c6f 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -189,6 +189,16 @@ where F: Float, StandardNormal: Distribution pub fn from_zscore(&self, zscore: F) -> F { self.mean + self.std_dev * zscore } + + /// Returns the mean (`μ`) of the distribution. + pub fn mean(&self) -> F { + self.mean + } + + /// Returns the standard deviation (`σ`) of the distribution. + pub fn std_dev(&self) -> F { + self.std_dev + } } impl Distribution for Normal From 90177320116c3c5eac46c72bcbbddafcd052c12e Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Sat, 10 Apr 2021 17:35:00 -0700 Subject: [PATCH 042/443] Add tests for advancing PCG generators A simple test to check the values after 20 steps. --- rand_pcg/tests/lcg128xsl64.rs | 12 ++++++++++++ rand_pcg/tests/lcg64xsh32.rs | 12 ++++++++++++ rand_pcg/tests/mcg128xsl64.rs | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index ac238b51623..8038d42671f 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -1,6 +1,18 @@ use rand_core::{RngCore, SeedableRng}; use rand_pcg::{Lcg128Xsl64, Pcg64}; +#[test] +fn test_lcg128xsl64_advancing() { + let seed = Default::default(); + let mut rng1 = Lcg128Xsl64::from_seed(seed); + let mut rng2 = Lcg128Xsl64::from_seed(seed); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1.next_u64(), rng2.next_u64()); +} + #[test] fn test_lcg128xsl64_construction() { // Test that various construction techniques produce a working RNG. diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index 24a06d32483..17d87858c2e 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -1,6 +1,18 @@ use rand_core::{RngCore, SeedableRng}; use rand_pcg::{Lcg64Xsh32, Pcg32}; +#[test] +fn test_lcg64xsh32_advancing() { + let seed = Default::default(); + let mut rng1 = Lcg64Xsh32::from_seed(seed); + let mut rng2 = Lcg64Xsh32::from_seed(seed); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1.next_u64(), rng2.next_u64()); +} + #[test] fn test_lcg64xsh32_construction() { // Test that various construction techniques produce a working RNG. diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index 32f363f350f..cf145503db5 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -1,6 +1,18 @@ use rand_core::{RngCore, SeedableRng}; use rand_pcg::{Mcg128Xsl64, Pcg64Mcg}; +#[test] +fn test_mcg128xsl64_advancing() { + let seed = Default::default(); + let mut rng1 = Mcg128Xsl64::from_seed(seed); + let mut rng2 = Mcg128Xsl64::from_seed(seed); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1.next_u64(), rng2.next_u64()); +} + #[test] fn test_mcg128xsl64_construction() { // Test that various construction techniques produce a working RNG. From 624d24e8ae0043bfbd59c369e1741ab3bc848a64 Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Fri, 16 Apr 2021 13:52:56 -0700 Subject: [PATCH 043/443] Delta needs to be doubled when advancing Lcg64Xsh32 --- rand_pcg/src/pcg64.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index ec30ad2aacb..abaa1079c12 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -55,8 +55,8 @@ impl Lcg64Xsh32 { let mut acc_plus: u64 = 0; let mut cur_mult = MULTIPLIER; let mut cur_plus = self.increment; - let mut mdelta = delta; - + let mut mdelta = delta.wrapping_mul(2); + while mdelta > 0 { if (mdelta & 1) != 0 { acc_mult = acc_mult.wrapping_mul(cur_mult); From f7960e9fb24c8599bd4e73de61254f25d7a6e1a9 Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Mon, 19 Apr 2021 14:00:18 -0700 Subject: [PATCH 044/443] Revert "Delta needs to be doubled when advancing Lcg64Xsh32" This reverts commit 624d24e8ae0043bfbd59c369e1741ab3bc848a64. --- rand_pcg/src/pcg64.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index abaa1079c12..ec30ad2aacb 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -55,8 +55,8 @@ impl Lcg64Xsh32 { let mut acc_plus: u64 = 0; let mut cur_mult = MULTIPLIER; let mut cur_plus = self.increment; - let mut mdelta = delta.wrapping_mul(2); - + let mut mdelta = delta; + while mdelta > 0 { if (mdelta & 1) != 0 { acc_mult = acc_mult.wrapping_mul(cur_mult); From 53b54b247e4d6579fcab8c644b5b74ed92abd41d Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Mon, 19 Apr 2021 14:12:23 -0700 Subject: [PATCH 045/443] Fix Lcg64Xsh32 advancing test to use 32 bit generation functions --- rand_pcg/tests/lcg64xsh32.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index 17d87858c2e..b57167780c3 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -7,10 +7,10 @@ fn test_lcg64xsh32_advancing() { let mut rng1 = Lcg64Xsh32::from_seed(seed); let mut rng2 = Lcg64Xsh32::from_seed(seed); for _ in 0..20 { - rng1.next_u64(); + rng1.next_u32(); } rng2.advance(20); - assert_eq!(rng1.next_u64(), rng2.next_u64()); + assert_eq!(rng1.next_u32(), rng2.next_u32()); } #[test] From c775705b882acfbb8a951f0bcfacdf9332fdf4cc Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Tue, 20 Apr 2021 20:38:38 -0700 Subject: [PATCH 046/443] Touch up changes around advancing feature * Add additional comments on how to use the advance() function * Use clone() for clarity instead of reusing a seed * Compare the generator as a whole as opposed the next value in unit tests --- rand_pcg/src/pcg128.rs | 6 ++++++ rand_pcg/src/pcg64.rs | 3 +++ rand_pcg/tests/lcg128xsl64.rs | 4 ++-- rand_pcg/tests/lcg64xsh32.rs | 4 ++-- rand_pcg/tests/mcg128xsl64.rs | 4 ++-- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index db006d22205..2c5b2495951 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -49,6 +49,9 @@ impl Lcg128Xsl64 { /// /// Even though delta is an unsigned integer, we can pass a /// signed integer to go backwards, it just goes "the long way round". + /// + /// Using this function is equivalent to calling `next_64()` `delta` + /// number of times. #[inline] pub fn advance(&mut self, delta: u128) { let mut acc_mult: u128 = 1; @@ -178,6 +181,9 @@ impl Mcg128Xsl64 { /// /// Even though delta is an unsigned integer, we can pass a /// signed integer to go backwards, it just goes "the long way round". + /// + /// Using this function is equivalent to calling `next_64()` `delta` + /// number of times. #[inline] pub fn advance(&mut self, delta: u128) { let mut acc_mult: u128 = 1; diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index ec30ad2aacb..dc157fc339d 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -49,6 +49,9 @@ impl Lcg64Xsh32 { /// /// Even though delta is an unsigned integer, we can pass a /// signed integer to go backwards, it just goes "the long way round". + /// + /// Using this function is equivalent to calling `next_32()` `delta` + /// number of times. #[inline] pub fn advance(&mut self, delta: u64) { let mut acc_mult: u64 = 1; diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index 8038d42671f..793e371dff5 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -5,12 +5,12 @@ use rand_pcg::{Lcg128Xsl64, Pcg64}; fn test_lcg128xsl64_advancing() { let seed = Default::default(); let mut rng1 = Lcg128Xsl64::from_seed(seed); - let mut rng2 = Lcg128Xsl64::from_seed(seed); + let mut rng2 = rng1.clone(); for _ in 0..20 { rng1.next_u64(); } rng2.advance(20); - assert_eq!(rng1.next_u64(), rng2.next_u64()); + assert_eq!(rng1, rng2); } #[test] diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index b57167780c3..b1b613f29b5 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -5,12 +5,12 @@ use rand_pcg::{Lcg64Xsh32, Pcg32}; fn test_lcg64xsh32_advancing() { let seed = Default::default(); let mut rng1 = Lcg64Xsh32::from_seed(seed); - let mut rng2 = Lcg64Xsh32::from_seed(seed); + let mut rng2 = rng1.clone(); for _ in 0..20 { rng1.next_u32(); } rng2.advance(20); - assert_eq!(rng1.next_u32(), rng2.next_u32()); + assert_eq!(rng1, rng2); } #[test] diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index cf145503db5..859f589278e 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -5,12 +5,12 @@ use rand_pcg::{Mcg128Xsl64, Pcg64Mcg}; fn test_mcg128xsl64_advancing() { let seed = Default::default(); let mut rng1 = Mcg128Xsl64::from_seed(seed); - let mut rng2 = Mcg128Xsl64::from_seed(seed); + let mut rng2 = rng1.clone(); for _ in 0..20 { rng1.next_u64(); } rng2.advance(20); - assert_eq!(rng1.next_u64(), rng2.next_u64()); + assert_eq!(rng1, rng2); } #[test] From 2c6223bec6a261df9e219958357579afe5a54b09 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 22 Apr 2021 02:30:52 +0200 Subject: [PATCH 047/443] rand_distr: Update changelog --- rand_distr/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 63cd1b9d761..05cd23f59f8 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +- Correctly document `no_std` support (#1100) +- Add `std_math` feature to prefer `std` over `libm` for floating point math (#1100) +- Add mean and std_dev accessors to Normal (#1114) + ## [0.4.0] - 2020-12-18 - Bump `rand` to v0.8.0 - New `Geometric`, `StandardGeometric` and `Hypergeometric` distributions (#1062) From 57cd95999d5a235da2bd4a662ec5d69431660e5b Mon Sep 17 00:00:00 2001 From: Mark Wong Date: Thu, 22 Apr 2021 16:32:08 -0700 Subject: [PATCH 048/443] Expand advancing tests to use multiple seeds --- rand_pcg/tests/lcg128xsl64.rs | 15 ++++++++------- rand_pcg/tests/lcg64xsh32.rs | 15 ++++++++------- rand_pcg/tests/mcg128xsl64.rs | 15 ++++++++------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index 793e371dff5..57f559a1390 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -3,14 +3,15 @@ use rand_pcg::{Lcg128Xsl64, Pcg64}; #[test] fn test_lcg128xsl64_advancing() { - let seed = Default::default(); - let mut rng1 = Lcg128Xsl64::from_seed(seed); - let mut rng2 = rng1.clone(); - for _ in 0..20 { - rng1.next_u64(); + for seed in 0..20 { + let mut rng1 = Lcg128Xsl64::seed_from_u64(seed); + let mut rng2 = rng1.clone(); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1, rng2); } - rng2.advance(20); - assert_eq!(rng1, rng2); } #[test] diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index b1b613f29b5..1efe5c58c33 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -3,14 +3,15 @@ use rand_pcg::{Lcg64Xsh32, Pcg32}; #[test] fn test_lcg64xsh32_advancing() { - let seed = Default::default(); - let mut rng1 = Lcg64Xsh32::from_seed(seed); - let mut rng2 = rng1.clone(); - for _ in 0..20 { - rng1.next_u32(); + for seed in 0..20 { + let mut rng1 = Lcg64Xsh32::seed_from_u64(seed); + let mut rng2 = rng1.clone(); + for _ in 0..20 { + rng1.next_u32(); + } + rng2.advance(20); + assert_eq!(rng1, rng2); } - rng2.advance(20); - assert_eq!(rng1, rng2); } #[test] diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index 859f589278e..c508a8ffa3d 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -3,14 +3,15 @@ use rand_pcg::{Mcg128Xsl64, Pcg64Mcg}; #[test] fn test_mcg128xsl64_advancing() { - let seed = Default::default(); - let mut rng1 = Mcg128Xsl64::from_seed(seed); - let mut rng2 = rng1.clone(); - for _ in 0..20 { - rng1.next_u64(); + for seed in 0..20 { + let mut rng1 = Mcg128Xsl64::seed_from_u64(seed); + let mut rng2 = rng1.clone(); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1, rng2); } - rng2.advance(20); - assert_eq!(rng1, rng2); } #[test] From c1e0a87044225f6c74a3b56007a03fc9cfcc8721 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 25 Apr 2021 15:57:11 -0300 Subject: [PATCH 049/443] Fix Miri tests Because Miri now runs the doctests as well, we have to make sure it does not run them for `no_std` targets, like we do for our normal tests. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d778d2d7ce..a5386b6187a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -150,7 +150,7 @@ jobs: rustup component add miri - name: Test rand run: | - cargo miri test --no-default-features + cargo miri test --no-default-features --lib --tests cargo miri test --features=log,small_rng cargo miri test --manifest-path rand_core/Cargo.toml cargo miri test --manifest-path rand_core/Cargo.toml --features=serde1 From a4e1d1c2d74e36787e2832710256d15cc4db919a Mon Sep 17 00:00:00 2001 From: Alexander Batischev Date: Thu, 29 Apr 2021 22:45:15 +0300 Subject: [PATCH 050/443] Bump "average" crate to 0.12 This fixes the following warnings (from rustc 1.51.1): warning: panic message is not a string literal --> rand_distr/tests/value_stability.rs:29:9 | 29 | assert_almost_eq!(self, rhs, 1e-6); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(non_fmt_panic)]` on by default = note: this is no longer accepted in Rust 2021 = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) warning: panic message is not a string literal --> rand_distr/tests/value_stability.rs:34:9 | 34 | assert_almost_eq!(self, rhs, 1e-14); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this is no longer accepted in Rust 2021 = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) warning: panic message is not a string literal --> rand_distr/tests/value_stability.rs:380:9 | 380 | assert_almost_eq!(a, b, 1e-5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this is no longer accepted in Rust 2021 = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index a3072b00310..5f0f4c8fca4 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -30,4 +30,4 @@ rand_pcg = { version = "0.3.0", path = "../rand_pcg" } # For inline examples rand = { path = "..", version = "0.8.0", default-features = false, features = ["std_rng", "std"] } # Histogram implementation for testing uniformity -average = "0.10.3" +average = { version = "0.12", features = [ "std" ] } From 25c1c49541bb3f8abe717e045f835559b0905496 Mon Sep 17 00:00:00 2001 From: Alexander Batischev Date: Thu, 29 Apr 2021 21:39:54 +0300 Subject: [PATCH 051/443] RngCore: explain what a pathological RNG will break Cf. https://github.com/rust-random/rngs/pull/25#discussion_r622770225. --- rand_core/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 7e847ae499a..c53d3195b21 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -76,12 +76,17 @@ pub mod le; /// [`next_u32`] or [`next_u64`] since the latter methods are almost always used /// with algorithmic generators (PRNGs), which are normally infallible. /// +/// Implementers should produce bits uniformly. Pathological RNGs (e.g. always +/// returning the same value, or never setting certain bits) can break rejection +/// sampling used by random distributions, and also break other RNGs when +/// seeding them via [`SeedableRng::from_rng`]. +/// /// Algorithmic generators implementing [`SeedableRng`] should normally have /// *portable, reproducible* output, i.e. fix Endianness when converting values /// to avoid platform differences, and avoid making any changes which affect /// output (except by communicating that the release has breaking changes). /// -/// Typically implementators will implement only one of the methods available +/// Typically an RNG will implement only one of the methods available /// in this trait directly, then use the helper functions from the /// [`impls`] module to implement the other methods. /// From 0f4931967d1a63a46411c03e863e8b9355961b14 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Fri, 7 May 2021 13:12:09 +0200 Subject: [PATCH 052/443] Ensure `Array64` is `repr(transparent)` Let me start by saying that I'm far from an expert when it comes to unsafe code in Rust. However, if I read [RFC 1758](https://github.com/rust-lang/rfcs/blob/master/text/1758-repr-transparent.md) correctly, it it seems that `Array64` should be marked `repr(transparent)`. This is because I believe it is considered an implementation detail that single-element tuple structs currently have the same representation as its single field: > As a matter of optimisation, eligible #[repr(Rust)] structs behave as if they were #[repr(transparent)] but as an implementation detail that can't be relied upon by users. With the transparent representation, the `generate` method can safely cast the `Array64` argument: ```rust // Fill slice of words by writing to equivalent slice of bytes, then fixing endianness. self.state.refill4($rounds, unsafe { &mut *(&mut *r as *mut Array64 as *mut [u8; 256]) }); ``` --- rand_chacha/src/chacha.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 17bcc5528d6..a3de5ed4267 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -24,6 +24,7 @@ const BUF_BLOCKS: u8 = 4; // number of 32-bit words per ChaCha block (fixed by algorithm definition) const BLOCK_WORDS: u8 = 16; +#[repr(transparent)] pub struct Array64([T; 64]); impl Default for Array64 where T: Default From fd50cd418a6f1022c4ad7fb5b6871b18b9cfc13c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 7 May 2021 17:44:08 -0300 Subject: [PATCH 053/443] Update dependency --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 5f0f4c8fca4..b923297bca2 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -30,4 +30,4 @@ rand_pcg = { version = "0.3.0", path = "../rand_pcg" } # For inline examples rand = { path = "..", version = "0.8.0", default-features = false, features = ["std_rng", "std"] } # Histogram implementation for testing uniformity -average = { version = "0.12", features = [ "std" ] } +average = { version = "0.13", features = [ "std" ] } From 0eb245415f4c24c7716a88db4d97f1eb29b0ae17 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 7 May 2021 17:44:47 -0300 Subject: [PATCH 054/443] Implement sparklines Those will only be used for testing distributions. --- rand_distr/tests/sparkline.rs | 118 ++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 rand_distr/tests/sparkline.rs diff --git a/rand_distr/tests/sparkline.rs b/rand_distr/tests/sparkline.rs new file mode 100644 index 00000000000..32ac18073b7 --- /dev/null +++ b/rand_distr/tests/sparkline.rs @@ -0,0 +1,118 @@ +/// Number of ticks. +const N: usize = 8; +/// Ticks used for the sparkline. +static TICKS: [char; N] = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; + +/// Render a sparkline of `data` into `buffer`. +pub fn render_u64(data: &[u64], buffer: &mut String) { + match data.len() { + 0 => { + return; + }, + 1 => { + if data[0] == 0 { + buffer.push(TICKS[0]); + } else { + buffer.push(TICKS[N - 1]); + } + return; + }, + _ => {}, + } + let max = data.iter().max().unwrap(); + let min = data.iter().min().unwrap(); + let scale = ((N - 1) as f64) / ((max - min) as f64); + for i in data { + let tick = (((i - min) as f64) * scale) as usize; + buffer.push(TICKS[tick]); + } +} + +/// Calculate the required capacity for the sparkline, given the length of the +/// input data. +pub fn required_capacity(len: usize) -> usize { + len * TICKS[0].len_utf8() +} + +/// Render a sparkline of `data` into a newly allocated string. +pub fn render_u64_as_string(data: &[u64]) -> String { + let cap = required_capacity(data.len()); + let mut s = String::with_capacity(cap); + render_u64(data, &mut s); + debug_assert_eq!(s.capacity(), cap); + s +} + +/// Render a sparkline of `data` into `buffer`. +pub fn render_f64(data: &[f64], buffer: &mut String) { + match data.len() { + 0 => { + return; + }, + 1 => { + if data[0] == 0. { + buffer.push(TICKS[0]); + } else { + buffer.push(TICKS[N - 1]); + } + return; + }, + _ => {}, + } + for x in data { + assert!(x.is_finite(), "can only render finite values"); + } + let max = data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)); + let min = data.iter().fold(f64::INFINITY, |a, &b| a.min(b)); + let scale = ((N - 1) as f64) / (max - min); + for x in data { + let tick = ((x - min) * scale) as usize; + buffer.push(TICKS[tick]); + } +} + +/// Render a sparkline of `data` into a newly allocated string. +pub fn render_f64_as_string(data: &[f64]) -> String { + let cap = required_capacity(data.len()); + let mut s = String::with_capacity(cap); + render_f64(data, &mut s); + debug_assert_eq!(s.capacity(), cap); + s +} + +#[cfg(test)] +mod tests { + #[test] + fn render_u64() { + let data = [2, 250, 670, 890, 2, 430, 11, 908, 123, 57]; + let mut s = String::with_capacity(super::required_capacity(data.len())); + super::render_u64(&data, &mut s); + println!("{}", s); + assert_eq!("▁▂▆▇▁▄▁█▁▁", &s); + } + + #[test] + fn render_u64_as_string() { + let data = [2, 250, 670, 890, 2, 430, 11, 908, 123, 57]; + let s = super::render_u64_as_string(&data); + println!("{}", s); + assert_eq!("▁▂▆▇▁▄▁█▁▁", &s); + } + + #[test] + fn render_f64() { + let data = [2., 250., 670., 890., 2., 430., 11., 908., 123., 57.]; + let mut s = String::with_capacity(super::required_capacity(data.len())); + super::render_f64(&data, &mut s); + println!("{}", s); + assert_eq!("▁▂▆▇▁▄▁█▁▁", &s); + } + + #[test] + fn render_f64_as_string() { + let data = [2., 250., 670., 890., 2., 430., 11., 908., 123., 57.]; + let s = super::render_f64_as_string(&data); + println!("{}", s); + assert_eq!("▁▂▆▇▁▄▁█▁▁", &s); + } +} From c8561f4d94a72fd68320d70ea6f7cc777eea8ad2 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 7 May 2021 17:47:40 -0300 Subject: [PATCH 055/443] rand_distr: Test normal distribution `Normal` is sampled many times into a histogram, which is then compared to the expected probability density function. To make debugging easier, sparklines of the expected distribution, the histogram, and their difference are printed. Currently, the test fails if the difference is significantly larger than the expected error of the histogram bin. However, the error estimate does not take the error in the normalization due to the finite width of the histogram into account. This should not be a problem, as long as the distribution is almost zero outside the range covered by the histogram. In principle, this approach can be generalized to other distributions. --- rand_distr/Cargo.toml | 2 +- rand_distr/tests/pdf.rs | 74 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 rand_distr/tests/pdf.rs diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index b923297bca2..38f5adfb820 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -28,6 +28,6 @@ std_math = ["num-traits/std"] [dev-dependencies] rand_pcg = { version = "0.3.0", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "0.8.0", default-features = false, features = ["std_rng", "std"] } +rand = { path = "..", version = "0.8.0", default-features = false, features = ["std_rng", "std", "small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.13", features = [ "std" ] } diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs new file mode 100644 index 00000000000..4a6634d61b9 --- /dev/null +++ b/rand_distr/tests/pdf.rs @@ -0,0 +1,74 @@ +use average::Histogram; +use rand::{Rng, SeedableRng}; +use rand_distr::Normal; + +average::define_histogram!(hist, 100); +use hist::Histogram as Histogram100; + +mod sparkline; + +#[test] +fn normal() { + const N_SAMPLES: u64 = 1_000_000; + const MEAN: f64 = 2.; + const STD_DEV: f64 = 0.5; + const MIN_X: f64 = -1.; + const MAX_X: f64 = 5.; + + let dist = Normal::new(MEAN, STD_DEV).unwrap(); + let mut hist = Histogram100::with_const_width(MIN_X,MAX_X); + let mut rng = rand::rngs::SmallRng::seed_from_u64(1); + + for _ in 0..N_SAMPLES { + let _ = hist.add(rng.sample(dist)); // Ignore out-of-range values + } + + println!("Sampled normal distribution:\n{}", + sparkline::render_u64_as_string(hist.bins())); + + fn pdf(x: f64) -> f64 { + (-0.5 * ((x - MEAN) / STD_DEV).powi(2)).exp() / + (STD_DEV * (2. * core::f64::consts::PI).sqrt()) + } + + let mut bin_centers = hist.centers(); + let mut expected = [0.; 100]; + for e in &mut expected { + *e = pdf(bin_centers.next().unwrap()); + } + + println!("Expected normal distribution:\n{}", + sparkline::render_u64_as_string(hist.bins())); + + let mut normalized_bins= hist.normalized_bins(); + let mut diff = [0.; 100]; + for i in 0..100 { + let bin = (normalized_bins.next().unwrap() as f64) / (N_SAMPLES as f64) ; + diff[i] = (bin - expected[i]).abs(); + } + + println!("Difference:\n{}", + sparkline::render_f64_as_string(&diff[..])); + println!("max diff: {:?}", diff.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b))); + + // Check that the differences are significantly smaller than the expected error. + let mut expected_error = [0.; 100]; + // Calculate error from histogram + for (err, var) in expected_error.iter_mut().zip(hist.variances()) { + *err = var.sqrt() / (N_SAMPLES as f64); + } + // Normalize error by bin width + for (err, width) in expected_error.iter_mut().zip(hist.widths()) { + *err /= width; + } + // TODO: Calculate error from distribution cutoff / normalization + + println!("max expected_error: {:?}", expected_error.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b))); + for (&d, &e) in diff.iter().zip(expected_error.iter()) { + // Difference larger than 3 standard deviations or cutoff + let tol = (3. * e).max(1e-4); + if d > tol { + panic!("Difference = {} * tol", d / tol); + } + } +} \ No newline at end of file From 9121d397321d6d0d4fa76e8b335d94a8d83f7a6f Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 7 May 2021 18:33:06 -0300 Subject: [PATCH 056/443] Document dependencies between PCG streams Fixes #907. --- rand_pcg/src/pcg128.rs | 8 +++++++- rand_pcg/src/pcg64.rs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index 2c5b2495951..f81b7fa8390 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -29,6 +29,9 @@ use rand_core::{le, Error, RngCore, SeedableRng}; /// Despite the name, this implementation uses 32 bytes (256 bit) space /// comprising 128 bits of state and 128 bits stream selector. These are both /// set by `SeedableRng`, using a 256-bit seed. +/// +/// Note that two generators with different stream parameters may be closely +/// correlated. #[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Lcg128Xsl64 { @@ -74,7 +77,10 @@ impl Lcg128Xsl64 { /// Construct an instance compatible with PCG seed and stream. /// - /// Note that PCG specifies default values for both parameters: + /// Note that two generators with different stream parameters may be closely + /// correlated. + /// + /// PCG specifies the following default values for both parameters: /// /// - `state = 0xcafef00dd15ea5e5` /// - `stream = 0xa02bdbf7bb3c0a7ac28fa16a64abf96` diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index dc157fc339d..602d7bb37fa 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -29,6 +29,9 @@ const MULTIPLIER: u64 = 6364136223846793005; /// Despite the name, this implementation uses 16 bytes (128 bit) space /// comprising 64 bits of state and 64 bits stream selector. These are both set /// by `SeedableRng`, using a 128-bit seed. +/// +/// Note that two generators with different stream parameter may be closely +/// correlated. #[derive(Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Lcg64Xsh32 { @@ -74,7 +77,10 @@ impl Lcg64Xsh32 { /// Construct an instance compatible with PCG seed and stream. /// - /// Note that PCG specifies default values for both parameters: + /// Note that two generators with different stream parameters may be closely + /// correlated. + /// + /// PCG specifies the following default values for both parameters: /// /// - `state = 0xcafef00dd15ea5e5` /// - `stream = 0xa02bdbf7bb3c0a7` From e8329a2d018d2bbe3c7f6624aaac2784f7ecba3f Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 7 May 2021 18:46:27 -0300 Subject: [PATCH 057/443] Fix tests for old Rust versions --- rand_distr/tests/pdf.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 4a6634d61b9..05896e996dc 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -49,7 +49,8 @@ fn normal() { println!("Difference:\n{}", sparkline::render_f64_as_string(&diff[..])); - println!("max diff: {:?}", diff.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b))); + println!("max diff: {:?}", diff.iter().fold( + core::f64::NEG_INFINITY, |a, &b| a.max(b))); // Check that the differences are significantly smaller than the expected error. let mut expected_error = [0.; 100]; @@ -63,7 +64,8 @@ fn normal() { } // TODO: Calculate error from distribution cutoff / normalization - println!("max expected_error: {:?}", expected_error.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b))); + println!("max expected_error: {:?}", expected_error.iter().fold( + core::f64::NEG_INFINITY, |a, &b| a.max(b))); for (&d, &e) in diff.iter().zip(expected_error.iter()) { // Difference larger than 3 standard deviations or cutoff let tol = (3. * e).max(1e-4); From da94e878193f9f142bf138e178143f3d2753fee5 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 8 May 2021 20:21:26 -0300 Subject: [PATCH 058/443] More fixes for old Rust versions --- rand_distr/tests/sparkline.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rand_distr/tests/sparkline.rs b/rand_distr/tests/sparkline.rs index 32ac18073b7..a49d57030df 100644 --- a/rand_distr/tests/sparkline.rs +++ b/rand_distr/tests/sparkline.rs @@ -62,8 +62,10 @@ pub fn render_f64(data: &[f64], buffer: &mut String) { for x in data { assert!(x.is_finite(), "can only render finite values"); } - let max = data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)); - let min = data.iter().fold(f64::INFINITY, |a, &b| a.min(b)); + let max = data.iter().fold( + core::f64::NEG_INFINITY, |a, &b| a.max(b)); + let min = data.iter().fold( + core::f64::INFINITY, |a, &b| a.min(b)); let scale = ((N - 1) as f64) / (max - min); for x in data { let tick = ((x - min) * scale) as usize; From 70efd2e180e2ca5d45ac156bf481ff3f1e018669 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 8 May 2021 20:33:46 -0300 Subject: [PATCH 059/443] Another fix for old Rust --- rand_distr/tests/pdf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 05896e996dc..62562527cd9 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -33,7 +33,7 @@ fn normal() { let mut bin_centers = hist.centers(); let mut expected = [0.; 100]; - for e in &mut expected { + for e in &mut expected[..] { *e = pdf(bin_centers.next().unwrap()); } From 0cde3bf20c70c34dbc04d14f04123a3fa6becc6f Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 8 May 2021 21:05:49 -0300 Subject: [PATCH 060/443] rand_distr: Fix clippy warnings --- rand_distr/benches/distributions.rs | 3 ++- rand_distr/src/cauchy.rs | 6 +++--- rand_distr/src/gamma.rs | 4 ++++ rand_distr/src/hypergeometric.rs | 2 +- rand_distr/src/weighted_alias.rs | 2 +- rand_distr/tests/pdf.rs | 2 ++ rand_distr/tests/uniformity.rs | 2 ++ 7 files changed, 15 insertions(+), 6 deletions(-) diff --git a/rand_distr/benches/distributions.rs b/rand_distr/benches/distributions.rs index 31e7e2fc716..e53a7b0310e 100644 --- a/rand_distr/benches/distributions.rs +++ b/rand_distr/benches/distributions.rs @@ -144,6 +144,7 @@ distr_int!(distr_geometric, u64, Geometric::new(0.5).unwrap()); distr_int!(distr_standard_geometric, u64, StandardGeometric); #[bench] +#[allow(clippy::approx_constant)] fn dist_iter(b: &mut Bencher) { let mut rng = Pcg64Mcg::from_entropy(); let distr = Normal::new(-2.71828, 3.14159).unwrap(); @@ -177,4 +178,4 @@ sample_binomial!(misc_binomial_1, 1, 0.9); sample_binomial!(misc_binomial_10, 10, 0.9); sample_binomial!(misc_binomial_100, 100, 0.99); sample_binomial!(misc_binomial_1000, 1000, 0.01); -sample_binomial!(misc_binomial_1e12, 1000_000_000_000, 0.2); +sample_binomial!(misc_binomial_1e12, 1_000_000_000_000, 0.2); diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index a68e12f7796..66dc09b3e7b 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -107,9 +107,9 @@ mod test { let mut rng = crate::test::rng(123); let mut numbers: [f64; 1000] = [0.0; 1000]; let mut sum = 0.0; - for i in 0..1000 { - numbers[i] = cauchy.sample(&mut rng); - sum += numbers[i]; + for number in &mut numbers[..] { + *number = cauchy.sample(&mut rng); + sum += *number; } let median = median(&mut numbers); #[cfg(feature = "std")] diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 0d2d87db9f0..45181b9204c 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -9,6 +9,10 @@ //! The Gamma and derived distributions. +// We use the variable names from the published reference, therefore this +// warning is not helpful. +#![allow(clippy::many_single_char_names)] + use self::ChiSquaredRepr::*; use self::GammaRepr::*; diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 406e97ea88a..512dd34c19b 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -221,7 +221,7 @@ impl Hypergeometric { } }; - Ok(Hypergeometric { n1, n2, k, sign_x, offset_x, sampling_method }) + Ok(Hypergeometric { n1, n2, k, offset_x, sign_x, sampling_method }) } } diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 1b07229be8c..53a9c2713d4 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -466,7 +466,7 @@ mod test { weights[ZERO_WEIGHT_INDEX as usize] = W::ZERO; weights }; - let weight_sum = weights.iter().map(|w| *w).sum::(); + let weight_sum = weights.iter().copied().sum::(); let expected_counts = weights .iter() .map(|&w| w_to_f64(w) / w_to_f64(weight_sum) * NUM_SAMPLES as f64) diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 62562527cd9..1dc6c7cae0e 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -1,3 +1,5 @@ +#![allow(clippy::float_cmp)] + use average::Histogram; use rand::{Rng, SeedableRng}; use rand_distr::Normal; diff --git a/rand_distr/tests/uniformity.rs b/rand_distr/tests/uniformity.rs index 7d359c7d733..4a64babdc76 100644 --- a/rand_distr/tests/uniformity.rs +++ b/rand_distr/tests/uniformity.rs @@ -6,6 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![allow(clippy::float_cmp)] + use average::Histogram; use rand::prelude::*; From 67f491a7a5ff65071a13f54d04d1de4b3d8e2df5 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 8 May 2021 21:35:09 -0300 Subject: [PATCH 061/443] rand_core: Fix clippy warnings --- rand_core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index c53d3195b21..bc24270771b 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -485,7 +485,7 @@ mod test { // This is the binomial distribution B(64, 0.5), so chance of // weight < 20 is binocdf(19, 64, 0.5) = 7.8e-4, and same for // weight > 44. - assert!(weight >= 20 && weight <= 44); + assert!((20..=44).contains(&weight)); for (i2, r2) in results.iter().enumerate() { if i1 == i2 { From aee00443d4cb6bbceea7c48aa31126add959ec24 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 8 May 2021 21:36:12 -0300 Subject: [PATCH 062/443] rand_hc: Fix clippy warnings --- rand_hc/src/hc128.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rand_hc/src/hc128.rs b/rand_hc/src/hc128.rs index 94d75778f75..cedd2cb6ba8 100644 --- a/rand_hc/src/hc128.rs +++ b/rand_hc/src/hc128.rs @@ -6,6 +6,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +// Disable some noisy clippy lints. +#![allow(clippy::many_single_char_names)] +#![allow(clippy::identity_op)] +// Disable a lint that cannot be fixed without increasing the MSRV +#![allow(clippy::op_ref)] + //! The HC-128 random number generator. use core::fmt; From 1b8eaf23eac5a9f149599f895132c71d500ea7b8 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 8 May 2021 21:37:15 -0300 Subject: [PATCH 063/443] rand: Fix clippy warnings --- benches/seq.rs | 28 ++++++++++++++-------------- src/distributions/bernoulli.rs | 5 ++++- src/distributions/mod.rs | 8 ++++---- src/distributions/other.rs | 10 +++++----- src/distributions/uniform.rs | 2 +- src/distributions/utils.rs | 2 ++ src/lib.rs | 2 +- src/rng.rs | 22 +++++++++++++--------- src/rngs/adapter/reseeding.rs | 2 ++ src/seq/index.rs | 6 +++--- src/seq/mod.rs | 4 ++-- 11 files changed, 51 insertions(+), 40 deletions(-) diff --git a/benches/seq.rs b/benches/seq.rs index 5b6fccf51ee..a9bd88ff882 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -37,8 +37,8 @@ fn seq_shuffle_100(b: &mut Bencher) { fn seq_slice_choose_1_of_1000(b: &mut Bencher) { let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); let x: &mut [usize] = &mut [1; 1000]; - for i in 0..1000 { - x[i] = i; + for (i, r) in x.iter_mut().enumerate() { + *r = i; } b.iter(|| { let mut s = 0; @@ -78,8 +78,8 @@ seq_slice_choose_multiple!(seq_slice_choose_multiple_90_of_100, 90, 100); fn seq_iter_choose_from_1000(b: &mut Bencher) { let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); let x: &mut [usize] = &mut [1; 1000]; - for i in 0..1000 { - x[i] = i; + for (i, r) in x.iter_mut().enumerate() { + *r = i; } b.iter(|| { let mut s = 0; @@ -172,11 +172,11 @@ macro_rules! sample_indices { sample_indices!(misc_sample_indices_1_of_1k, sample, 1, 1000); sample_indices!(misc_sample_indices_10_of_1k, sample, 10, 1000); sample_indices!(misc_sample_indices_100_of_1k, sample, 100, 1000); -sample_indices!(misc_sample_indices_100_of_1M, sample, 100, 1000_000); -sample_indices!(misc_sample_indices_100_of_1G, sample, 100, 1000_000_000); -sample_indices!(misc_sample_indices_200_of_1G, sample, 200, 1000_000_000); -sample_indices!(misc_sample_indices_400_of_1G, sample, 400, 1000_000_000); -sample_indices!(misc_sample_indices_600_of_1G, sample, 600, 1000_000_000); +sample_indices!(misc_sample_indices_100_of_1M, sample, 100, 1_000_000); +sample_indices!(misc_sample_indices_100_of_1G, sample, 100, 1_000_000_000); +sample_indices!(misc_sample_indices_200_of_1G, sample, 200, 1_000_000_000); +sample_indices!(misc_sample_indices_400_of_1G, sample, 400, 1_000_000_000); +sample_indices!(misc_sample_indices_600_of_1G, sample, 600, 1_000_000_000); macro_rules! sample_indices_rand_weights { ($name:ident, $amount:expr, $length:expr) => { @@ -193,8 +193,8 @@ macro_rules! sample_indices_rand_weights { sample_indices_rand_weights!(misc_sample_weighted_indices_1_of_1k, 1, 1000); sample_indices_rand_weights!(misc_sample_weighted_indices_10_of_1k, 10, 1000); sample_indices_rand_weights!(misc_sample_weighted_indices_100_of_1k, 100, 1000); -sample_indices_rand_weights!(misc_sample_weighted_indices_100_of_1M, 100, 1000_000); -sample_indices_rand_weights!(misc_sample_weighted_indices_200_of_1M, 200, 1000_000); -sample_indices_rand_weights!(misc_sample_weighted_indices_400_of_1M, 400, 1000_000); -sample_indices_rand_weights!(misc_sample_weighted_indices_600_of_1M, 600, 1000_000); -sample_indices_rand_weights!(misc_sample_weighted_indices_1k_of_1M, 1000, 1000_000); +sample_indices_rand_weights!(misc_sample_weighted_indices_100_of_1M, 100, 1_000_000); +sample_indices_rand_weights!(misc_sample_weighted_indices_200_of_1M, 200, 1_000_000); +sample_indices_rand_weights!(misc_sample_weighted_indices_400_of_1M, 400, 1_000_000); +sample_indices_rand_weights!(misc_sample_weighted_indices_600_of_1M, 600, 1_000_000); +sample_indices_rand_weights!(misc_sample_weighted_indices_1k_of_1M, 1000, 1_000_000); diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index b968ca046ed..d54d5992c48 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -96,7 +96,7 @@ impl Bernoulli { /// 2-64 in `[0, 1]` can be represented as a `f64`.) #[inline] pub fn new(p: f64) -> Result { - if !(p >= 0.0 && p < 1.0) { + if !(0.0..1.0).contains(&p) { if p == 1.0 { return Ok(Bernoulli { p_int: ALWAYS_TRUE }); } @@ -157,6 +157,9 @@ mod test { #[test] fn test_trivial() { + // We prefer to be explicit here. + #![allow(clippy::bool_assert_comparison)] + let mut r = crate::test::rng(1); let always_false = Bernoulli::new(0.0).unwrap(); let always_true = Bernoulli::new(1.0).unwrap(); diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 911d1604aae..ddf4f4fbc2c 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -358,9 +358,9 @@ mod tests { #[test] fn test_make_an_iter() { - fn ten_dice_rolls_other_than_five<'a, R: Rng>( - rng: &'a mut R, - ) -> impl Iterator + 'a { + fn ten_dice_rolls_other_than_five( + rng: &mut R, + ) -> impl Iterator + '_ { Uniform::new_inclusive(1, 6) .sample_iter(rng) .filter(|x| *x != 5) @@ -370,7 +370,7 @@ mod tests { let mut rng = crate::test::rng(211); let mut count = 0; for val in ten_dice_rolls_other_than_five(&mut rng) { - assert!(val >= 1 && val <= 6 && val != 5); + assert!((1..=6).contains(&val) && val != 5); count += 1; } assert_eq!(count, 10); diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 16b352ac382..9e58afd46e0 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -249,7 +249,7 @@ mod tests { .map(|()| rng.gen::()) .take(1000) .collect(); - assert!(word.len() != 0); + assert!(!word.is_empty()); } #[test] @@ -261,11 +261,11 @@ mod tests { let mut incorrect = false; for _ in 0..100 { let c: char = rng.sample(Alphanumeric).into(); - incorrect |= !((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') ); + incorrect |= !(('0'..='9').contains(&c) || + ('A'..='Z').contains(&c) || + ('a'..='z').contains(&c) ); } - assert!(incorrect == false); + assert!(!incorrect); } #[test] diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 57934838ae8..fb898370329 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1316,7 +1316,7 @@ mod tests { let mut max = core::char::from_u32(0).unwrap(); for _ in 0..100 { let c = rng.gen_range('A'..='Z'); - assert!('A' <= c && c <= 'Z'); + assert!(('A'..='Z').contains(&c)); max = max.max(c); } assert_eq!(max, 'Z'); diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index e3bceb8a96c..f8fb1cb1115 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -236,6 +236,8 @@ pub(crate) trait FloatSIMDUtils { /// Implement functions available in std builds but missing from core primitives #[cfg(not(std))] pub(crate) trait Float: Sized { + // False positive: We are following `std` here. + #![allow(clippy::wrong_self_convention)] fn is_nan(self) -> bool; fn is_infinite(self) -> bool; fn is_finite(self) -> bool; diff --git a/src/lib.rs b/src/lib.rs index 8bf7a9df126..6f8665a2d17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -201,10 +201,10 @@ mod test { #[test] #[cfg(all(feature = "std", feature = "std_rng"))] fn test_random() { - // not sure how to test this aside from just getting some values let _n: usize = random(); let _f: f32 = random(); let _o: Option> = random(); + #[allow(clippy::type_complexity)] let _many: ( (), (usize, isize, Option<(u32, (bool,))>), diff --git a/src/rng.rs b/src/rng.rs index f555a68586d..07643c55958 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -501,15 +501,15 @@ mod test { let mut r = rng(101); for _ in 0..1000 { let a = r.gen_range(-4711..17); - assert!(a >= -4711 && a < 17); - let a = r.gen_range(-3i8..42); - assert!(a >= -3i8 && a < 42i8); + assert!((-4711..17).contains(&a)); + let a: i8 = r.gen_range(-3..42); + assert!((-3..42).contains(&a)); let a: u16 = r.gen_range(10..99); - assert!(a >= 10u16 && a < 99u16); - let a = r.gen_range(-100i32..2000); - assert!(a >= -100i32 && a < 2000i32); + assert!((10..99).contains(&a)); + let a: i32 = r.gen_range(-100..2000); + assert!((-100..2000).contains(&a)); let a: u32 = r.gen_range(12..=24); - assert!(a >= 12u32 && a <= 24u32); + assert!((12..=24).contains(&a)); assert_eq!(r.gen_range(0u32..1), 0u32); assert_eq!(r.gen_range(-12i64..-11), -12i64); @@ -522,9 +522,9 @@ mod test { let mut r = rng(101); for _ in 0..1000 { let a = r.gen_range(-4.5..1.7); - assert!(a >= -4.5 && a < 1.7); + assert!((-4.5..1.7).contains(&a)); let a = r.gen_range(-1.1..=-0.3); - assert!(a >= -1.1 && a <= -0.3); + assert!((-1.1..=-0.3).contains(&a)); assert_eq!(r.gen_range(0.0f32..=0.0), 0.); assert_eq!(r.gen_range(-11.0..=-11.0), -11.); @@ -535,6 +535,7 @@ mod test { #[test] #[should_panic] fn test_gen_range_panic_int() { + #![allow(clippy::reversed_empty_ranges)] let mut r = rng(102); r.gen_range(5..-2); } @@ -542,12 +543,15 @@ mod test { #[test] #[should_panic] fn test_gen_range_panic_usize() { + #![allow(clippy::reversed_empty_ranges)] let mut r = rng(103); r.gen_range(5..2); } #[test] fn test_gen_bool() { + #![allow(clippy::bool_assert_comparison)] + let mut r = rng(105); for _ in 0..5 { assert_eq!(r.gen_bool(0.0), false); diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index 1977cb31906..70b0b82307f 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -355,6 +355,8 @@ mod test { #[test] fn test_clone_reseeding() { + #![allow(clippy::redundant_clone)] + let mut zero = StepRng::new(0, 0); let rng = Core::from_rng(&mut zero).unwrap(); let mut rng1 = ReseedingRng::new(rng, 32 * 4, zero); diff --git a/src/seq/index.rs b/src/seq/index.rs index 8b155e1f1e1..ae36c323708 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -630,7 +630,7 @@ mod test { match v { IndexVec::U32(mut indices) => { assert_eq!(indices.len(), amount); - indices.sort(); + indices.sort_unstable(); indices.dedup(); assert_eq!(indices.len(), amount); for &i in &indices { @@ -668,10 +668,10 @@ mod test { do_test(300, 80, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace do_test(300, 180, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace - do_test(1000_000, 8, &[ + do_test(1_000_000, 8, &[ 103717, 963485, 826422, 509101, 736394, 807035, 5327, 632573, ]); // floyd - do_test(1000_000, 180, &[ + do_test(1_000_000, 180, &[ 103718, 963490, 826426, 509103, 736396, 807036, 5327, 632573, ]); // rejection } diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 4375fa1ccc6..9eeb77749c9 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -991,8 +991,8 @@ mod test { move_last(&mut arr, pos); assert_eq!(arr[3], i); } - for i in 0..4 { - assert_eq!(arr[i], i); + for (i, &a) in arr.iter().enumerate() { + assert_eq!(a, i); } counts[permutation] += 1; } From ab6f3dd5cd22146a778c1c6a1cc9376a058d0ac6 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 8 May 2021 21:39:31 -0300 Subject: [PATCH 064/443] Add missing copyright headers --- rand_distr/tests/pdf.rs | 8 ++++++++ rand_distr/tests/sparkline.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 1dc6c7cae0e..185c7577acd 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -1,3 +1,11 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + #![allow(clippy::float_cmp)] use average::Histogram; diff --git a/rand_distr/tests/sparkline.rs b/rand_distr/tests/sparkline.rs index a49d57030df..6ba48ba886e 100644 --- a/rand_distr/tests/sparkline.rs +++ b/rand_distr/tests/sparkline.rs @@ -1,3 +1,11 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + /// Number of ticks. const N: usize = 8; /// Ticks used for the sparkline. From 8dac1dcb8244f85e52ee7e711de225bb23baad79 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 8 May 2021 21:46:02 -0300 Subject: [PATCH 065/443] Fix Rust 1.36 compatibility --- src/distributions/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index f8fb1cb1115..b11f602004e 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -235,9 +235,9 @@ pub(crate) trait FloatSIMDUtils { /// Implement functions available in std builds but missing from core primitives #[cfg(not(std))] +// False positive: We are following `std` here. +#[allow(clippy::wrong_self_convention)] pub(crate) trait Float: Sized { - // False positive: We are following `std` here. - #![allow(clippy::wrong_self_convention)] fn is_nan(self) -> bool; fn is_infinite(self) -> bool; fn is_finite(self) -> bool; From 76a7305f22ebd892e37ac58f6ab74e18d7a0eceb Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 9 May 2021 14:45:09 -0300 Subject: [PATCH 066/443] Define constant for histogram size --- rand_distr/tests/pdf.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 185c7577acd..d5ab177d98a 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -12,7 +12,8 @@ use average::Histogram; use rand::{Rng, SeedableRng}; use rand_distr::Normal; -average::define_histogram!(hist, 100); +const HIST_LEN: usize = 100; +average::define_histogram!(hist, crate::HIST_LEN); use hist::Histogram as Histogram100; mod sparkline; @@ -42,7 +43,7 @@ fn normal() { } let mut bin_centers = hist.centers(); - let mut expected = [0.; 100]; + let mut expected = [0.; HIST_LEN]; for e in &mut expected[..] { *e = pdf(bin_centers.next().unwrap()); } @@ -51,8 +52,8 @@ fn normal() { sparkline::render_u64_as_string(hist.bins())); let mut normalized_bins= hist.normalized_bins(); - let mut diff = [0.; 100]; - for i in 0..100 { + let mut diff = [0.; HIST_LEN]; + for i in 0..HIST_LEN { let bin = (normalized_bins.next().unwrap() as f64) / (N_SAMPLES as f64) ; diff[i] = (bin - expected[i]).abs(); } @@ -63,7 +64,7 @@ fn normal() { core::f64::NEG_INFINITY, |a, &b| a.max(b))); // Check that the differences are significantly smaller than the expected error. - let mut expected_error = [0.; 100]; + let mut expected_error = [0.; HIST_LEN]; // Calculate error from histogram for (err, var) in expected_error.iter_mut().zip(hist.variances()) { *err = var.sqrt() / (N_SAMPLES as f64); From 66da6e633a0de4a963f3c5a0c2f8d6b8bbe8e47c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 9 May 2021 14:55:35 -0300 Subject: [PATCH 067/443] Simplify loop --- rand_distr/tests/pdf.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index d5ab177d98a..b1d1aa28174 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -51,10 +51,9 @@ fn normal() { println!("Expected normal distribution:\n{}", sparkline::render_u64_as_string(hist.bins())); - let mut normalized_bins= hist.normalized_bins(); let mut diff = [0.; HIST_LEN]; - for i in 0..HIST_LEN { - let bin = (normalized_bins.next().unwrap() as f64) / (N_SAMPLES as f64) ; + for (i, n) in hist.normalized_bins().enumerate() { + let bin = (n as f64) / (N_SAMPLES as f64) ; diff[i] = (bin - expected[i]).abs(); } From 7f3108db62090384eeeaf746f65d6961059932ce Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Mon, 10 May 2021 14:30:32 -0700 Subject: [PATCH 068/443] chacha: some new accessors The new getters correspond to existing setters. --- rand_chacha/Cargo.toml | 2 +- rand_chacha/src/guts.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 0a653113d85..46facbd545e 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index 27ff957a92c..7f837246cf1 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -92,6 +92,11 @@ impl ChaCha { get_stream_param(self, param) } + #[inline(always)] + pub fn get_seed(&self) -> [u8; 32] { + get_seed(self) + } + /// Return whether rhs is equal in all parameters except current 64-bit position. #[inline] pub fn stream64_eq(&self, rhs: &Self) -> bool { @@ -205,6 +210,17 @@ dispatch_light128!(m, Mach, { } }); +dispatch_light128!(m, Mach, { + fn get_seed(state: &ChaCha) -> [u8; 32] { + let b: Mach::u32x4 = m.unpack(state.b); + let c: Mach::u32x4 = m.unpack(state.c); + let mut key = [0u8; 32]; + b.write_le(&mut key[..16]); + c.write_le(&mut key[16..]); + key + } +}); + fn read_u32le(xs: &[u8]) -> u32 { assert_eq!(xs.len(), 4); u32::from(xs[0]) | (u32::from(xs[1]) << 8) | (u32::from(xs[2]) << 16) | (u32::from(xs[3]) << 24) From d22607b65805314ce9ba6070c6f3d75128dcb6d3 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Mon, 10 May 2021 14:38:27 -0700 Subject: [PATCH 069/443] chacha: derive eq from a representation of the abstract state Equivalent to the previous implementation, but clearer in intent. The representable-abstract-state framework introduced here is general-purpose and can also be used e.g. for serialization. --- rand_chacha/src/chacha.rs | 66 +++++++++++++++++++++++++++++++++++---- rand_chacha/src/guts.rs | 8 ----- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index a3de5ed4267..b14475d8784 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -69,7 +69,7 @@ impl fmt::Debug for Array64 { } macro_rules! chacha_impl { - ($ChaChaXCore:ident, $ChaChaXRng:ident, $rounds:expr, $doc:expr) => { + ($ChaChaXCore:ident, $ChaChaXRng:ident, $rounds:expr, $doc:expr, $abst:ident) => { #[doc=$doc] #[derive(Clone, PartialEq, Eq)] pub struct $ChaChaXCore { @@ -245,6 +245,24 @@ macro_rules! chacha_impl { self.set_word_pos(wp); } } + + /// Get the stream number. + #[inline] + pub fn get_stream(&self) -> u64 { + self.rng + .core + .state + .get_stream_param(STREAM_PARAM_NONCE) + } + + /// Get the seed. + #[inline] + pub fn get_seed(&self) -> [u8; 32] { + self.rng + .core + .state + .get_seed() + } } impl CryptoRng for $ChaChaXRng {} @@ -259,17 +277,53 @@ macro_rules! chacha_impl { impl PartialEq<$ChaChaXRng> for $ChaChaXRng { fn eq(&self, rhs: &$ChaChaXRng) -> bool { - self.rng.core.state.stream64_eq(&rhs.rng.core.state) - && self.get_word_pos() == rhs.get_word_pos() + let a: $abst::$ChaChaXRng = self.into(); + let b: $abst::$ChaChaXRng = rhs.into(); + a == b } } impl Eq for $ChaChaXRng {} + + mod $abst { + // The abstract state of a ChaCha stream, independent of implementation choices. The + // comparison and serialization of this object is considered a semver-covered part of + // the API. + #[derive(Debug, PartialEq, Eq)] + pub(crate) struct $ChaChaXRng { + seed: [u8; 32], + stream: u64, + word_pos: u128, + } + + impl From<&super::$ChaChaXRng> for $ChaChaXRng { + // Forget all information about the input except what is necessary to determine the + // outputs of any sequence of pub API calls. + fn from(r: &super::$ChaChaXRng) -> Self { + Self { + seed: r.get_seed(), + stream: r.get_stream(), + word_pos: r.get_word_pos(), + } + } + } + + impl From<&$ChaChaXRng> for super::$ChaChaXRng { + // Construct one of the possible concrete RNGs realizing an abstract state. + fn from(a: &$ChaChaXRng) -> Self { + use rand_core::SeedableRng; + let mut r = Self::from_seed(a.seed); + r.set_stream(a.stream); + r.set_word_pos(a.word_pos); + r + } + } + } } } -chacha_impl!(ChaCha20Core, ChaCha20Rng, 10, "ChaCha with 20 rounds"); -chacha_impl!(ChaCha12Core, ChaCha12Rng, 6, "ChaCha with 12 rounds"); -chacha_impl!(ChaCha8Core, ChaCha8Rng, 4, "ChaCha with 8 rounds"); +chacha_impl!(ChaCha20Core, ChaCha20Rng, 10, "ChaCha with 20 rounds", abstract20); +chacha_impl!(ChaCha12Core, ChaCha12Rng, 6, "ChaCha with 12 rounds", abstract12); +chacha_impl!(ChaCha8Core, ChaCha8Rng, 4, "ChaCha with 8 rounds", abstract8); #[cfg(test)] mod test { diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index 7f837246cf1..992d1d0aadf 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -96,14 +96,6 @@ impl ChaCha { pub fn get_seed(&self) -> [u8; 32] { get_seed(self) } - - /// Return whether rhs is equal in all parameters except current 64-bit position. - #[inline] - pub fn stream64_eq(&self, rhs: &Self) -> bool { - let self_d: [u32; 4] = self.d.into(); - let rhs_d: [u32; 4] = rhs.d.into(); - self.b == rhs.b && self.c == rhs.c && self_d[3] == rhs_d[3] && self_d[2] == rhs_d[2] - } } #[allow(clippy::many_single_char_names)] From 22029be3e306f4cbea5d3b71bdd1ef64cfafec13 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Mon, 10 May 2021 14:29:23 -0700 Subject: [PATCH 070/443] chacha: optional serde The format is semver-stable. --- rand_chacha/Cargo.toml | 2 ++ rand_chacha/src/chacha.rs | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 46facbd545e..3d941af3cf3 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -17,8 +17,10 @@ edition = "2018" [dependencies] rand_core = { path = "../rand_core", version = "0.6.0" } ppv-lite86 = { version = "0.2.8", default-features = false, features = ["simd"] } +serde = { version = "1.0", features = ["derive"], optional = true } [features] default = ["std"] std = ["ppv-lite86/std"] simd = [] # deprecated +serde1 = ["serde"] diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index b14475d8784..76738df28db 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -16,6 +16,8 @@ use crate::guts::ChaCha; use rand_core::block::{BlockRng, BlockRngCore}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; +#[cfg(feature = "serde1")] use serde::{Serialize, Deserialize, Serializer, Deserializer}; + const STREAM_PARAM_NONCE: u32 = 1; const STREAM_PARAM_BLOCK: u32 = 0; @@ -284,11 +286,31 @@ macro_rules! chacha_impl { } impl Eq for $ChaChaXRng {} + #[cfg(feature = "serde1")] + impl Serialize for $ChaChaXRng { + fn serialize(&self, s: S) -> Result + where S: Serializer { + $abst::$ChaChaXRng::from(self).serialize(s) + } + } + #[cfg(feature = "serde1")] + impl<'de> Deserialize<'de> for $ChaChaXRng { + fn deserialize(d: D) -> Result where D: Deserializer<'de> { + $abst::$ChaChaXRng::deserialize(d).map(|x| Self::from(&x)) + } + } + mod $abst { + #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; + // The abstract state of a ChaCha stream, independent of implementation choices. The // comparison and serialization of this object is considered a semver-covered part of // the API. #[derive(Debug, PartialEq, Eq)] + #[cfg_attr( + feature = "serde1", + derive(Serialize, Deserialize), + )] pub(crate) struct $ChaChaXRng { seed: [u8; 32], stream: u64, From d7a9e303149a36698ef33d048eda1cd45cc8ff62 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Mon, 10 May 2021 14:48:33 -0700 Subject: [PATCH 071/443] chacha: test for serde roundtrip --- rand_chacha/Cargo.toml | 4 ++++ rand_chacha/src/chacha.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 3d941af3cf3..56ecbf7e1a6 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -19,6 +19,10 @@ rand_core = { path = "../rand_core", version = "0.6.0" } ppv-lite86 = { version = "0.2.8", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } +[dev-dependencies] +# Only to test serde1 +bincode = "1.2.1" + [features] default = ["std"] std = ["ppv-lite86/std"] diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 76738df28db..de766c50787 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -351,8 +351,38 @@ chacha_impl!(ChaCha8Core, ChaCha8Rng, 4, "ChaCha with 8 rounds", abstract8); mod test { use rand_core::{RngCore, SeedableRng}; + #[cfg(feature = "serde1")] use super::{ChaCha20Rng, ChaCha12Rng, ChaCha8Rng}; + type ChaChaRng = super::ChaCha20Rng; + #[cfg(feature = "serde1")] + #[test] + fn test_chacha_serde_roundtrip() { + let seed = [ + 1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, 0, 0, 0, 0, + 0, 2, 92, + ]; + let mut rng1 = ChaCha20Rng::from_seed(seed); + let mut rng2 = ChaCha12Rng::from_seed(seed); + let mut rng3 = ChaCha8Rng::from_seed(seed); + + let encoded1 = bincode::serialize(&rng1).unwrap(); + let encoded2 = bincode::serialize(&rng2).unwrap(); + let encoded3 = bincode::serialize(&rng3).unwrap(); + + let mut decoded1: ChaCha20Rng = bincode::deserialize(&encoded1).unwrap(); + let mut decoded2: ChaCha12Rng = bincode::deserialize(&encoded2).unwrap(); + let mut decoded3: ChaCha8Rng = bincode::deserialize(&encoded3).unwrap(); + + assert_eq!(rng1, decoded1); + assert_eq!(rng2, decoded2); + assert_eq!(rng3, decoded3); + + assert_eq!(rng1.next_u32(), decoded1.next_u32()); + assert_eq!(rng2.next_u32(), decoded2.next_u32()); + assert_eq!(rng3.next_u32(), decoded3.next_u32()); + } + #[test] fn test_chacha_construction() { let seed = [ From 31a1d56aeaa67cb5676d9df6e81df618550c502c Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Mon, 10 May 2021 15:55:36 -0700 Subject: [PATCH 072/443] chacha: add a test for serde format stability --- rand_chacha/Cargo.toml | 2 +- rand_chacha/src/chacha.rs | 31 +++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 56ecbf7e1a6..53f9f591107 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -21,7 +21,7 @@ serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] # Only to test serde1 -bincode = "1.2.1" +serde_json = "1.0" [features] default = ["std"] diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index de766c50787..5c1c6742e97 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -366,13 +366,13 @@ mod test { let mut rng2 = ChaCha12Rng::from_seed(seed); let mut rng3 = ChaCha8Rng::from_seed(seed); - let encoded1 = bincode::serialize(&rng1).unwrap(); - let encoded2 = bincode::serialize(&rng2).unwrap(); - let encoded3 = bincode::serialize(&rng3).unwrap(); + let encoded1 = serde_json::to_string(&rng1).unwrap(); + let encoded2 = serde_json::to_string(&rng2).unwrap(); + let encoded3 = serde_json::to_string(&rng3).unwrap(); - let mut decoded1: ChaCha20Rng = bincode::deserialize(&encoded1).unwrap(); - let mut decoded2: ChaCha12Rng = bincode::deserialize(&encoded2).unwrap(); - let mut decoded3: ChaCha8Rng = bincode::deserialize(&encoded3).unwrap(); + let mut decoded1: ChaCha20Rng = serde_json::from_str(&encoded1).unwrap(); + let mut decoded2: ChaCha12Rng = serde_json::from_str(&encoded2).unwrap(); + let mut decoded3: ChaCha8Rng = serde_json::from_str(&encoded3).unwrap(); assert_eq!(rng1, decoded1); assert_eq!(rng2, decoded2); @@ -383,6 +383,25 @@ mod test { assert_eq!(rng3.next_u32(), decoded3.next_u32()); } + // This test validates that: + // 1. a hard-coded serialization demonstrating the format at time of initial release can still + // be deserialized to a ChaChaRng + // 2. re-serializing the resultant object produces exactly the original string + // + // Condition 2 is stronger than necessary: an equivalent serialization (e.g. with field order + // permuted, or whitespace differences) would also be admissible, but would fail this test. + // However testing for equivalence of serialized data is difficult, and there shouldn't be any + // reason we need to violate the stronger-than-needed condition, e.g. by changing the field + // definition order. + #[cfg(feature = "serde1")] + #[test] + fn test_chacha_serde_format_stability() { + let j = r#"{"seed":[4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8],"stream":27182818284,"word_pos":314159265359}"#; + let r: ChaChaRng = serde_json::from_str(&j).unwrap(); + let j1 = serde_json::to_string(&r).unwrap(); + assert_eq!(j, j1); + } + #[test] fn test_chacha_construction() { let seed = [ From eb8823642ffc9894bfa7b6181d89798106ea278c Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Wed, 12 May 2021 09:49:11 -0700 Subject: [PATCH 073/443] chacha: update changelog --- rand_chacha/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index 8a073900765..e1ccf61aa5a 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.1] - 2021-05-12 +- add getters corresponding to existing setters: `get_seed`, `get_state` +- add serde support, gated by the `serde1` feature + ## [0.3.0] - 2020-12-08 - Bump `rand_core` version to 0.6.0 - Bump MSRV to 1.36 (#1011) From 2f4bdfc2e115b71140b37403042acf10ccf8fc62 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Wed, 12 May 2021 09:12:52 -0700 Subject: [PATCH 074/443] chacha: hide numbered-param interface in guts --- rand_chacha/src/chacha.rs | 11 ++++------- rand_chacha/src/guts.rs | 21 +++++++++++++++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 5c1c6742e97..50da81bfafe 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -18,9 +18,6 @@ use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize, Serializer, Deserializer}; -const STREAM_PARAM_NONCE: u32 = 1; -const STREAM_PARAM_BLOCK: u32 = 0; - // NB. this must remain consistent with some currently hard-coded numbers in this module const BUF_BLOCKS: u8 = 4; // number of 32-bit words per ChaCha block (fixed by algorithm definition) @@ -196,7 +193,7 @@ macro_rules! chacha_impl { #[inline] pub fn get_word_pos(&self) -> u128 { let buf_start_block = { - let buf_end_block = self.rng.core.state.get_stream_param(STREAM_PARAM_BLOCK); + let buf_end_block = self.rng.core.state.get_block_pos(); u64::wrapping_sub(buf_end_block, BUF_BLOCKS.into()) }; let (buf_offset_blocks, block_offset_words) = { @@ -221,7 +218,7 @@ macro_rules! chacha_impl { self.rng .core .state - .set_stream_param(STREAM_PARAM_BLOCK, block); + .set_block_pos(block); self.rng.generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); } @@ -241,7 +238,7 @@ macro_rules! chacha_impl { self.rng .core .state - .set_stream_param(STREAM_PARAM_NONCE, stream); + .set_nonce(stream); if self.rng.index() != 64 { let wp = self.get_word_pos(); self.set_word_pos(wp); @@ -254,7 +251,7 @@ macro_rules! chacha_impl { self.rng .core .state - .get_stream_param(STREAM_PARAM_NONCE) + .get_nonce() } /// Get the seed. diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index 992d1d0aadf..cee8cf75d4c 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -21,6 +21,9 @@ const BUFBLOCKS: u64 = 1 << LOG2_BUFBLOCKS; pub(crate) const BUFSZ64: u64 = BLOCK64 * BUFBLOCKS; pub(crate) const BUFSZ: usize = BUFSZ64 as usize; +const STREAM_PARAM_NONCE: u32 = 1; +const STREAM_PARAM_BLOCK: u32 = 0; + #[derive(Clone, PartialEq, Eq)] pub struct ChaCha { pub(crate) b: vec128_storage, @@ -83,13 +86,23 @@ impl ChaCha { } #[inline(always)] - pub fn set_stream_param(&mut self, param: u32, value: u64) { - set_stream_param(self, param, value) + pub fn set_block_pos(&mut self, value: u64) { + set_stream_param(self, STREAM_PARAM_BLOCK, value) + } + + #[inline(always)] + pub fn get_block_pos(&self) -> u64 { + get_stream_param(self, STREAM_PARAM_BLOCK) + } + + #[inline(always)] + pub fn set_nonce(&mut self, value: u64) { + set_stream_param(self, STREAM_PARAM_NONCE, value) } #[inline(always)] - pub fn get_stream_param(&self, param: u32) -> u64 { - get_stream_param(self, param) + pub fn get_nonce(&self) -> u64 { + get_stream_param(self, STREAM_PARAM_NONCE) } #[inline(always)] From 4d4ae255410ceed552edf5877f2f2937195c3924 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 12 May 2021 13:55:15 -0300 Subject: [PATCH 075/443] Add missing licence header --- src/distributions/slice.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index a39e63c1c50..e5e3fe26744 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -1,4 +1,10 @@ // Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. use crate::distributions::{Distribution, Uniform}; From 13949eeccd3946829d065feaefd4abf205a51dea Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 12 May 2021 13:59:41 -0300 Subject: [PATCH 076/443] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 536c510af81..9a64f0403ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [0.8.4] - unreleased +### Distributions +- Add slice distribution (#1107) + ## [0.8.3] - 2021-01-25 ### Fixes - Fix `no-std` + `alloc` build by gating `choose_multiple_weighted` on `std` (#1088) From 6bfc5a88e68d8394f4fb4c3f6709280e03238bf3 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 12 May 2021 14:38:22 -0300 Subject: [PATCH 077/443] `Slice`: Derive `Clone` and `Copy` --- src/distributions/slice.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index e5e3fe26744..023ddac26dc 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -61,7 +61,7 @@ use crate::distributions::{Distribution, Uniform}; /// [`SliceRandom`]: crate::seq::SliceRandom /// [`SliceRandom::choose`]: crate::seq::SliceRandom::choose /// [`SliceRandom::choose_multiple`]: crate::seq::SliceRandom::choose_multiple -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Slice<'a, T> { slice: &'a [T], range: Uniform, @@ -81,14 +81,6 @@ impl<'a, T> Slice<'a, T> { } } -impl Clone for Slice<'_, T> { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Slice<'_, T> {} - impl<'a, T> Distribution<&'a T> for Slice<'a, T> { fn sample(&self, rng: &mut R) -> &'a T { let idx = self.range.sample(rng); From 701c2c77c8d44c64d3850579206efb266b65fe35 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 12 May 2021 14:41:24 -0300 Subject: [PATCH 078/443] `Slice::new`: Fix return type documentation --- src/distributions/slice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 023ddac26dc..3302deb2a40 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -69,7 +69,7 @@ pub struct Slice<'a, T> { impl<'a, T> Slice<'a, T> { /// Create a new `Slice` instance which samples uniformly from the slice. - /// Returns `None` if the slice is empty. + /// Returns `Err` if the slice is empty. pub fn new(slice: &'a [T]) -> Result { match slice.len() { 0 => Err(EmptySlice), From 3f210e75f72dd9acdd7e2e2217490e6f10982a1f Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 13 May 2021 14:41:46 -0300 Subject: [PATCH 079/443] Implement `Clone` and `Debug` for `Alphanumeric` --- src/distributions/other.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 9e58afd46e0..d1f060f483d 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -57,7 +57,7 @@ use std::mem::{self, MaybeUninit}; /// /// - [Wikipedia article on Password Strength](https://en.wikipedia.org/wiki/Password_strength) /// - [Diceware for generating memorable passwords](https://en.wikipedia.org/wiki/Diceware) -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Alphanumeric; From 2c32fe0b0451207fd5ff337e6dc30ec4c0376c50 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 13 May 2021 14:45:30 -0300 Subject: [PATCH 080/443] Implement `Clone` and `Debug` for `{Normal,}InverseGaussian` --- rand_distr/src/inverse_gaussian.rs | 2 +- rand_distr/src/normal_inverse_gaussian.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 7af645a23c4..195becdf28a 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -12,7 +12,7 @@ pub enum Error { } /// The [inverse Gaussian distribution](https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution) -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct InverseGaussian where F: Float, diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index 252a319d877..5ccc3b64cf6 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -12,7 +12,7 @@ pub enum Error { } /// The [normal-inverse Gaussian distribution](https://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct NormalInverseGaussian where F: Float, From 18bea4ca94de1e07dfc24ee4181eb1ab4949eb45 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 13 May 2021 14:52:23 -0300 Subject: [PATCH 081/443] Add missing impls to `*InverseGaussian` errors --- rand_distr/src/inverse_gaussian.rs | 2 +- rand_distr/src/normal_inverse_gaussian.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 195becdf28a..5b8678b982e 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -3,7 +3,7 @@ use num_traits::Float; use rand::Rng; /// Error type returned from `InverseGaussian::new` -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { /// `mean <= 0` or `nan`. MeanNegativeOrNull, diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index 5ccc3b64cf6..630c2021fbc 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -3,7 +3,7 @@ use num_traits::Float; use rand::Rng; /// Error type returned from `NormalInverseGaussian::new` -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { /// `alpha <= 0` or `nan`. AlphaNegativeOrNull, From 17dbc4c8b7808a2d50c7b205534ba7b6dfb07016 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 13 May 2021 16:21:31 -0300 Subject: [PATCH 082/443] Improve error formatting for `WeightedError` --- src/distributions/weighted_index.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 07ba53ec027..32da37f6cd3 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -439,15 +439,15 @@ pub enum WeightedError { } #[cfg(feature = "std")] -impl ::std::error::Error for WeightedError {} +impl std::error::Error for WeightedError {} impl fmt::Display for WeightedError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - WeightedError::NoItem => write!(f, "No weights provided."), - WeightedError::InvalidWeight => write!(f, "A weight is invalid."), - WeightedError::AllWeightsZero => write!(f, "All weights are zero."), - WeightedError::TooMany => write!(f, "Too many weights (hit u32::MAX)"), - } + f.write_str(match *self { + WeightedError::NoItem => "No weights provided in distribution", + WeightedError::InvalidWeight => "A weight is invalid in distribution", + WeightedError::AllWeightsZero => "All weights are zero in distribution", + WeightedError::TooMany => "Too many weights (hit u32::MAX) in distribution", + }) } } From f952177fcbeb46aa8edd7502cce30f9902631cbb Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 13 May 2021 16:22:37 -0300 Subject: [PATCH 083/443] Implement `Error` and `Display` for all errors --- rand_distr/src/hypergeometric.rs | 4 ++++ rand_distr/src/inverse_gaussian.rs | 14 ++++++++++++++ rand_distr/src/normal_inverse_gaussian.rs | 14 ++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 512dd34c19b..507d7bb07f5 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -73,6 +73,10 @@ impl fmt::Display for Error { } } +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + // evaluate fact(numerator.0)*fact(numerator.1) / fact(denominator.0)*fact(denominator.1) fn fraction_of_products_of_factorials(numerator: (u64, u64), denominator: (u64, u64)) -> f64 { let min_top = u64::min(numerator.0, numerator.1); diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 5b8678b982e..becb02b64f8 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -1,6 +1,7 @@ use crate::{Distribution, Standard, StandardNormal}; use num_traits::Float; use rand::Rng; +use core::fmt; /// Error type returned from `InverseGaussian::new` #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -11,6 +12,19 @@ pub enum Error { ShapeNegativeOrNull, } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::MeanNegativeOrNull => "mean <= 0 or is NaN in inverse Gaussian distribution", + Error::ShapeNegativeOrNull => "shape <= 0 or is NaN in inverse Gaussian distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + /// The [inverse Gaussian distribution](https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution) #[derive(Debug, Clone, Copy)] pub struct InverseGaussian diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index 630c2021fbc..d8e44587d05 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -1,6 +1,7 @@ use crate::{Distribution, InverseGaussian, Standard, StandardNormal}; use num_traits::Float; use rand::Rng; +use core::fmt; /// Error type returned from `NormalInverseGaussian::new` #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -11,6 +12,19 @@ pub enum Error { AbsoluteBetaNotLessThanAlpha, } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::AlphaNegativeOrNull => "alpha <= 0 or is NaN in normal inverse Gaussian distribution", + Error::AbsoluteBetaNotLessThanAlpha => "|beta| >= alpha or is NaN in normal inverse Gaussian distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + /// The [normal-inverse Gaussian distribution](https://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) #[derive(Debug, Clone, Copy)] pub struct NormalInverseGaussian From 35b098ea7a82afe1badb92691d5e3e1073810a15 Mon Sep 17 00:00:00 2001 From: Jake Vossen Date: Thu, 13 May 2021 15:31:38 -0600 Subject: [PATCH 084/443] fixed broken link to sample_iter --- src/distributions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 911d1604aae..0c88f66da35 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -142,7 +142,7 @@ mod utils; /// of The Rust Rand Book. In some cases this does not apply, e.g. the `usize` /// type requires different sampling on 32-bit and 64-bit machines. /// -/// [`sample_iter`]: Distribution::method.sample_iter +/// [`sample_iter`]: Distribution::sample_iter pub trait Distribution { /// Generate a random value of `T`, using `rng` as the source of randomness. fn sample(&self, rng: &mut R) -> T; From f14cdeac169bb98373020816164b35b9dc4ccfdf Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 13 May 2021 18:38:24 -0300 Subject: [PATCH 085/443] Update changelogs --- CHANGELOG.md | 1 + rand_distr/CHANGELOG.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e2838c3229..4945f5e8390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [Unreleased] ### Additions - Use const-generics to support arrays of all sizes (#1104) +- Implement `Clone` and `Copy` for `Alphanumeric` (#1126) ### Other - Reorder asserts in `Uniform` float distributions for easier debugging of non-finite arguments diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 05cd23f59f8..346be3c26bb 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Correctly document `no_std` support (#1100) - Add `std_math` feature to prefer `std` over `libm` for floating point math (#1100) - Add mean and std_dev accessors to Normal (#1114) +- Make sure all distributions and their error types implement `Error`, `Display`, `Clone`, + `Copy`, `PartialEq` and `Eq` as appropriate (#1126) ## [0.4.0] - 2020-12-18 - Bump `rand` to v0.8.0 From e0e13d79c60d72dee2ae417c4b7f320437f1fc6a Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sun, 23 May 2021 08:09:41 +0200 Subject: [PATCH 086/443] Create a distribution by mapping the output of another one This is useful if consumers are to be given an opaque type implementing the Distribution trait, but the output of the provided implementations needs additional post processing, e.g. to attach compile time units of measurement. --- CHANGELOG.md | 1 + src/distributions/mod.rs | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4945f5e8390..e8baa39e002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ### Additions - Use const-generics to support arrays of all sizes (#1104) - Implement `Clone` and `Copy` for `Alphanumeric` (#1126) +- Add `Distribution::map` to derive a distribution using a closure (#1129) ### Other - Reorder asserts in `Uniform` float distributions for easier debugging of non-finite arguments diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 8171e30e43c..d1ae30c9402 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -199,6 +199,35 @@ pub trait Distribution { phantom: ::core::marker::PhantomData, } } + + /// Create a distribution of values of 'S' by mapping the output of `Self` + /// through the closure `F` + /// + /// # Example + /// + /// ``` + /// use rand::thread_rng; + /// use rand::distributions::{Distribution, Uniform}; + /// + /// let mut rng = thread_rng(); + /// + /// let die = Uniform::new_inclusive(1, 6); + /// let even_number = die.map(|num| num % 2 == 0); + /// while !even_number.sample(&mut rng) { + /// println!("Still odd; rolling again!"); + /// } + /// ``` + fn map(self, func: F) -> DistMap + where + F: Fn(T) -> S, + Self: Sized, + { + DistMap { + distr: self, + func, + phantom: ::core::marker::PhantomData, + } + } } impl<'a, T, D: Distribution> Distribution for &'a D { @@ -256,6 +285,28 @@ where { } +/// A distribution of values of type `S` derived from the distribution `D` +/// by mapping its output of type `T` through the closure `F`. +/// +/// This `struct` is created by the [`Distribution::map`] method. +/// See its documentation for more. +#[derive(Debug)] +pub struct DistMap { + distr: D, + func: F, + phantom: ::core::marker::PhantomData S>, +} + +impl Distribution for DistMap +where + D: Distribution, + F: Fn(T) -> S, +{ + fn sample(&self, rng: &mut R) -> S { + (self.func)(self.distr.sample(rng)) + } +} + /// A generic random value distribution, implemented for many primitive types. /// Usually generates values with a numerically uniform distribution, and with a /// range appropriate to the type. @@ -360,6 +411,15 @@ mod tests { assert!(0. < sum && sum < 100.); } + #[test] + fn test_distributions_map() { + let dist = Uniform::new_inclusive(0, 5).map(|val| val + 15); + + let mut rng = crate::test::rng(212); + let val = dist.sample(&mut rng); + assert!(val >= 15 && val <= 20); + } + #[test] fn test_make_an_iter() { fn ten_dice_rolls_other_than_five( From a6623ccbf7d1aa33b159fef65244b3873f6936b2 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 25 Apr 2021 15:29:18 -0300 Subject: [PATCH 087/443] rand_distr: Port benchmarks to Criterion - The benchmarks are now living in their own crate. Therefore, this does not add any dev-dependencies to rand_distr. - Instead of bytes per seconds, we now measure cycles per byte. Refs #1039. --- rand_distr/benches/Cargo.toml | 22 +++ rand_distr/benches/distributions.rs | 181 -------------------- rand_distr/benches/src/distributions.rs | 219 ++++++++++++++++++++++++ 3 files changed, 241 insertions(+), 181 deletions(-) create mode 100644 rand_distr/benches/Cargo.toml delete mode 100644 rand_distr/benches/distributions.rs create mode 100644 rand_distr/benches/src/distributions.rs diff --git a/rand_distr/benches/Cargo.toml b/rand_distr/benches/Cargo.toml new file mode 100644 index 00000000000..093286d57df --- /dev/null +++ b/rand_distr/benches/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "benches" +version = "0.0.0" +authors = ["The Rand Project Developers"] +license = "MIT OR Apache-2.0" +description = "Criterion benchmarks of the rand_distr crate" +edition = "2018" +publish = false + +[workspace] + +[dependencies] +criterion = { version = "0.3", features = ["html_reports"] } +criterion-cycles-per-byte = "0.1" +rand = { path = "../../" } +rand_distr = { path = "../" } +rand_pcg = { path = "../../rand_pcg/" } + +[[bench]] +name = "distributions" +path = "src/distributions.rs" +harness = false \ No newline at end of file diff --git a/rand_distr/benches/distributions.rs b/rand_distr/benches/distributions.rs deleted file mode 100644 index e53a7b0310e..00000000000 --- a/rand_distr/benches/distributions.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![feature(custom_inner_attributes)] -#![feature(test)] - -// Rustfmt splits macro invocations to shorten lines; in this case longer-lines are more readable -#![rustfmt::skip] - -extern crate test; - -const RAND_BENCH_N: u64 = 1000; - -use std::mem::size_of; -use test::Bencher; - -use rand::prelude::*; -use rand_distr::*; - -// At this time, distributions are optimised for 64-bit platforms. -use rand_pcg::Pcg64Mcg; - -macro_rules! distr_int { - ($fnn:ident, $ty:ty, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); - let distr = $distr; - - b.iter(|| { - let mut accum = 0 as $ty; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum = accum.wrapping_add(x); - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -macro_rules! distr_float { - ($fnn:ident, $ty:ty, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); - let distr = $distr; - - b.iter(|| { - let mut accum = 0.0; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum += x; - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -macro_rules! distr { - ($fnn:ident, $ty:ty, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); - let distr = $distr; - - b.iter(|| { - let mut accum = 0u32; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum = accum.wrapping_add(x as u32); - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -macro_rules! distr_arr { - ($fnn:ident, $ty:ty, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); - let distr = $distr; - - b.iter(|| { - let mut accum = 0u32; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum = accum.wrapping_add(x[0] as u32); - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - - -// distributions -distr_float!(distr_exp, f64, Exp::new(1.23 * 4.56).unwrap()); -distr_float!(distr_exp1_specialized, f64, Exp1); -distr_float!(distr_exp1_general, f64, Exp::new(1.).unwrap()); -distr_float!(distr_normal, f64, Normal::new(-1.23, 4.56).unwrap()); -distr_float!(distr_standardnormal_specialized, f64, StandardNormal); -distr_float!(distr_standardnormal_general, f64, Normal::new(0., 1.).unwrap()); -distr_float!(distr_log_normal, f64, LogNormal::new(-1.23, 4.56).unwrap()); -distr_float!(distr_gamma_large_shape, f64, Gamma::new(10., 1.0).unwrap()); -distr_float!(distr_gamma_small_shape, f64, Gamma::new(0.1, 1.0).unwrap()); -distr_float!(distr_beta_small_param, f64, Beta::new(0.1, 0.1).unwrap()); -distr_float!(distr_beta_large_param_similar, f64, Beta::new(101., 95.).unwrap()); -distr_float!(distr_beta_large_param_different, f64, Beta::new(10., 1000.).unwrap()); -distr_float!(distr_beta_mixed_param, f64, Beta::new(0.5, 100.).unwrap()); -distr_float!(distr_cauchy, f64, Cauchy::new(4.2, 6.9).unwrap()); -distr_float!(distr_triangular, f64, Triangular::new(0., 1., 0.9).unwrap()); -distr_int!(distr_binomial, u64, Binomial::new(20, 0.7).unwrap()); -distr_int!(distr_binomial_small, u64, Binomial::new(1000000, 1e-30).unwrap()); -distr_float!(distr_poisson, f64, Poisson::new(4.0).unwrap()); -distr!(distr_bernoulli, bool, Bernoulli::new(0.18).unwrap()); -distr_arr!(distr_circle, [f64; 2], UnitCircle); -distr_arr!(distr_sphere, [f64; 3], UnitSphere); - -// Weighted -distr_int!(distr_weighted_i8, usize, WeightedIndex::new(&[1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); -distr_int!(distr_weighted_u32, usize, WeightedIndex::new(&[1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); -distr_int!(distr_weighted_f64, usize, WeightedIndex::new(&[1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); -distr_int!(distr_weighted_large_set, usize, WeightedIndex::new((0..10000).rev().chain(1..10001)).unwrap()); - -distr_int!(distr_weighted_alias_method_i8, usize, WeightedAliasIndex::new(vec![1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); -distr_int!(distr_weighted_alias_method_u32, usize, WeightedAliasIndex::new(vec![1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); -distr_int!(distr_weighted_alias_method_f64, usize, WeightedAliasIndex::new(vec![1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); -distr_int!(distr_weighted_alias_method_large_set, usize, WeightedAliasIndex::new((0..10000).rev().chain(1..10001).collect()).unwrap()); - -distr_int!(distr_geometric, u64, Geometric::new(0.5).unwrap()); -distr_int!(distr_standard_geometric, u64, StandardGeometric); - -#[bench] -#[allow(clippy::approx_constant)] -fn dist_iter(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); - let distr = Normal::new(-2.71828, 3.14159).unwrap(); - let mut iter = distr.sample_iter(&mut rng); - - b.iter(|| { - let mut accum = 0.0; - for _ in 0..RAND_BENCH_N { - accum += iter.next().unwrap(); - } - accum - }); - b.bytes = size_of::() as u64 * RAND_BENCH_N; -} - -macro_rules! sample_binomial { - ($name:ident, $n:expr, $p:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); - let (n, p) = ($n, $p); - b.iter(|| { - let d = Binomial::new(n, p).unwrap(); - rng.sample(d) - }) - } - }; -} - -sample_binomial!(misc_binomial_1, 1, 0.9); -sample_binomial!(misc_binomial_10, 10, 0.9); -sample_binomial!(misc_binomial_100, 100, 0.99); -sample_binomial!(misc_binomial_1000, 1000, 0.01); -sample_binomial!(misc_binomial_1e12, 1_000_000_000_000, 0.2); diff --git a/rand_distr/benches/src/distributions.rs b/rand_distr/benches/src/distributions.rs new file mode 100644 index 00000000000..621f351868c --- /dev/null +++ b/rand_distr/benches/src/distributions.rs @@ -0,0 +1,219 @@ +// Copyright 2018-2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(custom_inner_attributes)] + +// Rustfmt splits macro invocations to shorten lines; in this case longer-lines are more readable +#![rustfmt::skip] + +const RAND_BENCH_N: u64 = 1000; + +use criterion::{criterion_group, criterion_main, Criterion, + Throughput}; +use criterion_cycles_per_byte::CyclesPerByte; + +use std::mem::size_of; + +use rand::prelude::*; +use rand_distr::*; + +// At this time, distributions are optimised for 64-bit platforms. +use rand_pcg::Pcg64Mcg; + +macro_rules! distr_int { + ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { + $group.throughput(Throughput::Bytes( + size_of::<$ty>() as u64 * RAND_BENCH_N)); + $group.bench_function($fnn, |c| { + let mut rng = Pcg64Mcg::from_entropy(); + let distr = $distr; + + c.iter(|| { + let mut accum: $ty = 0; + for _ in 0..RAND_BENCH_N { + let x: $ty = distr.sample(&mut rng); + accum = accum.wrapping_add(x); + } + accum + }); + }); + }; +} + +macro_rules! distr_float { + ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { + $group.throughput(Throughput::Bytes( + size_of::<$ty>() as u64 * RAND_BENCH_N)); + $group.bench_function($fnn, |c| { + let mut rng = Pcg64Mcg::from_entropy(); + let distr = $distr; + + c.iter(|| { + let mut accum = 0.; + for _ in 0..RAND_BENCH_N { + let x: $ty = distr.sample(&mut rng); + accum += x; + } + accum + }); + }); + }; +} + +macro_rules! distr { + ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { + $group.throughput(Throughput::Bytes( + size_of::<$ty>() as u64 * RAND_BENCH_N)); + $group.bench_function($fnn, |c| { + let mut rng = Pcg64Mcg::from_entropy(); + let distr = $distr; + + c.iter(|| { + let mut accum: u32 = 0; + for _ in 0..RAND_BENCH_N { + let x: $ty = distr.sample(&mut rng); + accum = accum.wrapping_add(x as u32); + } + accum + }); + }); + }; +} + +macro_rules! distr_arr { + ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { + $group.throughput(Throughput::Bytes( + size_of::<$ty>() as u64 * RAND_BENCH_N)); + $group.bench_function($fnn, |c| { + let mut rng = Pcg64Mcg::from_entropy(); + let distr = $distr; + + c.iter(|| { + let mut accum: u32 = 0; + for _ in 0..RAND_BENCH_N { + let x: $ty = distr.sample(&mut rng); + accum = accum.wrapping_add(x[0] as u32); + } + accum + }); + }); + }; +} + +macro_rules! sample_binomial { + ($group:ident, $name:expr, $n:expr, $p:expr) => { + distr_int!($group, $name, u64, Binomial::new($n, $p).unwrap()) + }; +} + +fn bench(c: &mut Criterion) { + { + let mut g = c.benchmark_group("exp"); + distr_float!(g, "exp", f64, Exp::new(1.23 * 4.56).unwrap()); + distr_float!(g, "exp1_specialized", f64, Exp1); + distr_float!(g, "exp1_general", f64, Exp::new(1.).unwrap()); + } + + { + let mut g = c.benchmark_group("normal"); + distr_float!(g, "normal", f64, Normal::new(-1.23, 4.56).unwrap()); + distr_float!(g, "standardnormal_specialized", f64, StandardNormal); + distr_float!(g, "standardnormal_general", f64, Normal::new(0., 1.).unwrap()); + distr_float!(g, "log_normal", f64, LogNormal::new(-1.23, 4.56).unwrap()); + g.throughput(Throughput::Bytes(size_of::() as u64 * RAND_BENCH_N)); + g.bench_function("iter", |c| { + let mut rng = Pcg64Mcg::from_entropy(); + let distr = Normal::new(-2.71828, 3.14159).unwrap(); + let mut iter = distr.sample_iter(&mut rng); + + c.iter(|| { + let mut accum = 0.0; + for _ in 0..RAND_BENCH_N { + accum += iter.next().unwrap(); + } + accum + }); + }); + } + + { + let mut g = c.benchmark_group("gamma"); + distr_float!(g, "gamma_large_shape", f64, Gamma::new(10., 1.0).unwrap()); + distr_float!(g, "gamma_small_shape", f64, Gamma::new(0.1, 1.0).unwrap()); + distr_float!(g, "beta_small_param", f64, Beta::new(0.1, 0.1).unwrap()); + distr_float!(g, "beta_large_param_similar", f64, Beta::new(101., 95.).unwrap()); + distr_float!(g, "beta_large_param_different", f64, Beta::new(10., 1000.).unwrap()); + distr_float!(g, "beta_mixed_param", f64, Beta::new(0.5, 100.).unwrap()); + } + + { + let mut g = c.benchmark_group("cauchy"); + distr_float!(g, "cauchy", f64, Cauchy::new(4.2, 6.9).unwrap()); + } + + { + let mut g = c.benchmark_group("triangular"); + distr_float!(g, "triangular", f64, Triangular::new(0., 1., 0.9).unwrap()); + } + + { + let mut g = c.benchmark_group("geometric"); + distr_int!(g, "geometric", u64, Geometric::new(0.5).unwrap()); + distr_int!(g, "standard_geometric", u64, StandardGeometric); + } + + { + let mut g = c.benchmark_group("weighted"); + distr_int!(g, "weighted_i8", usize, WeightedIndex::new(&[1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "weighted_u32", usize, WeightedIndex::new(&[1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "weighted_f64", usize, WeightedIndex::new(&[1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); + distr_int!(g, "weighted_large_set", usize, WeightedIndex::new((0..10000).rev().chain(1..10001)).unwrap()); + distr_int!(g, "weighted_alias_method_i8", usize, WeightedAliasIndex::new(vec![1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "weighted_alias_method_u32", usize, WeightedAliasIndex::new(vec![1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "weighted_alias_method_f64", usize, WeightedAliasIndex::new(vec![1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); + distr_int!(g, "weighted_alias_method_large_set", usize, WeightedAliasIndex::new((0..10000).rev().chain(1..10001).collect()).unwrap()); + } + + { + let mut g = c.benchmark_group("binomial"); + sample_binomial!(g, "binomial", 20, 0.7); + sample_binomial!(g, "binomial_small", 1_000_000, 1e-30); + sample_binomial!(g, "binomial_1", 1, 0.9); + sample_binomial!(g, "binomial_10", 10, 0.9); + sample_binomial!(g, "binomial_100", 100, 0.99); + sample_binomial!(g, "binomial_1000", 1000, 0.01); + sample_binomial!(g, "binomial_1e12", 1000_000_000_000, 0.2); + } + + { + let mut g = c.benchmark_group("poisson"); + distr_float!(g, "poisson", f64, Poisson::new(4.0).unwrap()); + } + + { + let mut g = c.benchmark_group("bernoulli"); + distr!(g, "bernoulli", bool, Bernoulli::new(0.18).unwrap()); + } + + { + let mut g = c.benchmark_group("circle"); + distr_arr!(g, "circle", [f64; 2], UnitCircle); + } + + { + let mut g = c.benchmark_group("sphere"); + distr_arr!(g, "sphere", [f64; 3], UnitSphere); + } +} + +criterion_group!( + name = benches; + config = Criterion::default().with_measurement(CyclesPerByte); + targets = bench +); +criterion_main!(benches); From f6bbfcfa899a681138d52e7e1e27fe4d2e45d5f7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 27 May 2021 10:17:01 +0100 Subject: [PATCH 088/443] serde for BlockRng, ReseedingRng and ReadRng --- Cargo.toml | 2 +- rand_core/src/block.rs | 6 ++++++ src/rngs/adapter/read.rs | 1 + src/rngs/adapter/reseeding.rs | 10 ++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a1bfb5b5efa..ce949e016df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] # Meta-features: default = ["std", "std_rng"] nightly = [] # enables performance optimizations requiring nightly rust -serde1 = ["serde"] +serde1 = ["serde", "rand_core/serde1"] # Option (enabled by default): without "std" rand uses libcore; this option # enables functionality expected to be available on a standard platform. diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 005d071fbb6..a54cadfed7d 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -114,6 +114,12 @@ pub trait BlockRngCore { /// [`try_fill_bytes`]: RngCore::try_fill_bytes #[derive(Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde1", + serde( + bound = "for<'x> R: Serialize + Deserialize<'x> + Sized, for<'x> R::Results: Serialize + Deserialize<'x>" + ) +)] pub struct BlockRng { results: R::Results, index: usize, diff --git a/src/rngs/adapter/read.rs b/src/rngs/adapter/read.rs index 63b0dd0c0f0..c623e4e13be 100644 --- a/src/rngs/adapter/read.rs +++ b/src/rngs/adapter/read.rs @@ -44,6 +44,7 @@ use rand_core::{impls, Error, RngCore}; /// [`OsRng`]: crate::rngs::OsRng /// [`try_fill_bytes`]: RngCore::try_fill_bytes #[derive(Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct ReadRng { reader: R, } diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index 70b0b82307f..36b492a57f3 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -14,6 +14,8 @@ use core::mem::size_of; use rand_core::block::{BlockRng, BlockRngCore}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; /// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the /// ability to reseed it. @@ -76,6 +78,13 @@ use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// [`ReseedingRng::new`]: ReseedingRng::new /// [`reseed()`]: ReseedingRng::reseed #[derive(Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde1", + serde( + bound = "for<'x> R: Serialize + Deserialize<'x> + Sized, for<'x> R::Results: Serialize + Deserialize<'x>, for<'x> Rsdr: Serialize + Deserialize<'x>" + ) +)] pub struct ReseedingRng(BlockRng>) where R: BlockRngCore + SeedableRng, @@ -148,6 +157,7 @@ where } #[derive(Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct ReseedingCore { inner: R, reseeder: Rsdr, From 4726d328d6f4d02762cd5026f98a09c4bd107afa Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 27 May 2021 11:00:13 +0100 Subject: [PATCH 089/443] Update minimum version of packed_simd_2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ce949e016df..7111e48f6da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ serde = { version = "1.0.103", features = ["derive"], optional = true } [dependencies.packed_simd] # NOTE: so far no version works reliably due to dependence on unstable features package = "packed_simd_2" -version = "0.3.4" +version = "0.3.5" optional = true features = ["into_bits"] From d9c6a760485ff4490beed18ed3629c08a82d5c22 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 31 May 2021 17:43:57 +0100 Subject: [PATCH 090/443] README: add note regarding wasm32-unknown-unknown Addresses #1110 and #1131. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index aaf6df1d584..355e53bdac9 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,14 @@ unavailable (unless `getrandom` is enabled), large parts of `seq` are unavailable (unless `alloc` is enabled), and `thread_rng` and `random` are unavailable. +### WASM support + +The WASM target `wasm32-unknown-unknown` is not *automatically* supported by +`rand` or `getrandom`. To solve this, either use a different target such as +`wasm32-wasi` or add a direct dependancy on `getrandom` with the `js` feature +(if the target supports JavaScript). See +[getrandom#WebAssembly support](https://docs.rs/getrandom/latest/getrandom/#webassembly-support). + # License Rand is distributed under the terms of both the MIT license and the From e3bc4a1357b7ccf023af268f1d2a828926713e6f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Jun 2021 17:50:03 +0100 Subject: [PATCH 091/443] Do not impl serde for ReadRng or ReseedingRng --- src/rngs/adapter/read.rs | 1 - src/rngs/adapter/reseeding.rs | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/src/rngs/adapter/read.rs b/src/rngs/adapter/read.rs index c623e4e13be..63b0dd0c0f0 100644 --- a/src/rngs/adapter/read.rs +++ b/src/rngs/adapter/read.rs @@ -44,7 +44,6 @@ use rand_core::{impls, Error, RngCore}; /// [`OsRng`]: crate::rngs::OsRng /// [`try_fill_bytes`]: RngCore::try_fill_bytes #[derive(Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct ReadRng { reader: R, } diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index 36b492a57f3..70b0b82307f 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -14,8 +14,6 @@ use core::mem::size_of; use rand_core::block::{BlockRng, BlockRngCore}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; /// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the /// ability to reseed it. @@ -78,13 +76,6 @@ use serde::{Deserialize, Serialize}; /// [`ReseedingRng::new`]: ReseedingRng::new /// [`reseed()`]: ReseedingRng::reseed #[derive(Debug)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -#[cfg_attr( - feature = "serde1", - serde( - bound = "for<'x> R: Serialize + Deserialize<'x> + Sized, for<'x> R::Results: Serialize + Deserialize<'x>, for<'x> Rsdr: Serialize + Deserialize<'x>" - ) -)] pub struct ReseedingRng(BlockRng>) where R: BlockRngCore + SeedableRng, @@ -157,7 +148,6 @@ where } #[derive(Debug)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct ReseedingCore { inner: R, reseeder: Rsdr, From d167dd25d20b6b6e56fd565879c3b532625a42e5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Jun 2021 17:50:34 +0100 Subject: [PATCH 092/443] Deprecate ReadRng --- src/rngs/adapter/mod.rs | 1 + src/rngs/adapter/read.rs | 15 ++++----------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/rngs/adapter/mod.rs b/src/rngs/adapter/mod.rs index 22b7158d40f..bd1d2943233 100644 --- a/src/rngs/adapter/mod.rs +++ b/src/rngs/adapter/mod.rs @@ -11,5 +11,6 @@ mod read; mod reseeding; +#[allow(deprecated)] pub use self::read::{ReadError, ReadRng}; pub use self::reseeding::ReseedingRng; diff --git a/src/rngs/adapter/read.rs b/src/rngs/adapter/read.rs index 63b0dd0c0f0..25a9ca7fca4 100644 --- a/src/rngs/adapter/read.rs +++ b/src/rngs/adapter/read.rs @@ -9,6 +9,8 @@ //! A wrapper around any Read to treat it as an RNG. +#![allow(deprecated)] + use std::fmt; use std::io::Read; @@ -30,20 +32,10 @@ use rand_core::{impls, Error, RngCore}; /// have enough data, will only be reported through [`try_fill_bytes`]. /// The other [`RngCore`] methods will panic in case of an error. /// -/// # Example -/// -/// ``` -/// use rand::Rng; -/// use rand::rngs::adapter::ReadRng; -/// -/// let data = vec![1, 2, 3, 4, 5, 6, 7, 8]; -/// let mut rng = ReadRng::new(&data[..]); -/// println!("{:x}", rng.gen::()); -/// ``` -/// /// [`OsRng`]: crate::rngs::OsRng /// [`try_fill_bytes`]: RngCore::try_fill_bytes #[derive(Debug)] +#[deprecated(since="0.8.4", note="removal due to lack of usage")] pub struct ReadRng { reader: R, } @@ -86,6 +78,7 @@ impl RngCore for ReadRng { /// `ReadRng` error type #[derive(Debug)] +#[deprecated(since="0.8.4")] pub struct ReadError(std::io::Error); impl fmt::Display for ReadError { From a7f8fb72d7f36a07499a12d4d6fe811f47fcca78 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 9 Jun 2021 15:21:20 +0100 Subject: [PATCH 093/443] Prepare rand_chacha v0.3.1 release --- rand_chacha/CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index e1ccf61aa5a..a598bb7ee6c 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,9 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.3.1] - 2021-05-12 -- add getters corresponding to existing setters: `get_seed`, `get_state` -- add serde support, gated by the `serde1` feature +## [0.3.1] - 2021-06-09 +- add getters corresponding to existing setters: `get_seed`, `get_stream` (#1124) +- add serde support, gated by the `serde1` feature (#1124) +- ensure expected layout via `repr(transparent)` (#1120) ## [0.3.0] - 2020-12-08 - Bump `rand_core` version to 0.6.0 From 1947c899f797d9e082be128ef6b75c57e0384f39 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Jun 2021 18:10:28 +0100 Subject: [PATCH 094/443] Move Distribution trait and associates to sub-module --- src/distributions/distribution.rs | 236 +++++++++++++++++++++++++ src/distributions/mod.rs | 274 +++--------------------------- 2 files changed, 260 insertions(+), 250 deletions(-) create mode 100644 src/distributions/distribution.rs diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs new file mode 100644 index 00000000000..412c91f736e --- /dev/null +++ b/src/distributions/distribution.rs @@ -0,0 +1,236 @@ +// Copyright 2018 Developers of the Rand project. +// Copyright 2013-2017 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Distribution trait and associates + +use crate::Rng; +use core::iter; + +/// Types (distributions) that can be used to create a random instance of `T`. +/// +/// It is possible to sample from a distribution through both the +/// `Distribution` and [`Rng`] traits, via `distr.sample(&mut rng)` and +/// `rng.sample(distr)`. They also both offer the [`sample_iter`] method, which +/// produces an iterator that samples from the distribution. +/// +/// All implementations are expected to be immutable; this has the significant +/// advantage of not needing to consider thread safety, and for most +/// distributions efficient state-less sampling algorithms are available. +/// +/// Implementations are typically expected to be portable with reproducible +/// results when used with a PRNG with fixed seed; see the +/// [portability chapter](https://rust-random.github.io/book/portability.html) +/// of The Rust Rand Book. In some cases this does not apply, e.g. the `usize` +/// type requires different sampling on 32-bit and 64-bit machines. +/// +/// [`sample_iter`]: Distribution::sample_iter +pub trait Distribution { + /// Generate a random value of `T`, using `rng` as the source of randomness. + fn sample(&self, rng: &mut R) -> T; + + /// Create an iterator that generates random values of `T`, using `rng` as + /// the source of randomness. + /// + /// Note that this function takes `self` by value. This works since + /// `Distribution` is impl'd for `&D` where `D: Distribution`, + /// however borrowing is not automatic hence `distr.sample_iter(...)` may + /// need to be replaced with `(&distr).sample_iter(...)` to borrow or + /// `(&*distr).sample_iter(...)` to reborrow an existing reference. + /// + /// # Example + /// + /// ``` + /// use rand::thread_rng; + /// use rand::distributions::{Distribution, Alphanumeric, Uniform, Standard}; + /// + /// let mut rng = thread_rng(); + /// + /// // Vec of 16 x f32: + /// let v: Vec = Standard.sample_iter(&mut rng).take(16).collect(); + /// + /// // String: + /// let s: String = Alphanumeric + /// .sample_iter(&mut rng) + /// .take(7) + /// .map(char::from) + /// .collect(); + /// + /// // Dice-rolling: + /// let die_range = Uniform::new_inclusive(1, 6); + /// let mut roll_die = die_range.sample_iter(&mut rng); + /// while roll_die.next().unwrap() != 6 { + /// println!("Not a 6; rolling again!"); + /// } + /// ``` + fn sample_iter(self, rng: R) -> DistIter + where + R: Rng, + Self: Sized, + { + DistIter { + distr: self, + rng, + phantom: ::core::marker::PhantomData, + } + } + + /// Create a distribution of values of 'S' by mapping the output of `Self` + /// through the closure `F` + /// + /// # Example + /// + /// ``` + /// use rand::thread_rng; + /// use rand::distributions::{Distribution, Uniform}; + /// + /// let mut rng = thread_rng(); + /// + /// let die = Uniform::new_inclusive(1, 6); + /// let even_number = die.map(|num| num % 2 == 0); + /// while !even_number.sample(&mut rng) { + /// println!("Still odd; rolling again!"); + /// } + /// ``` + fn map(self, func: F) -> DistMap + where + F: Fn(T) -> S, + Self: Sized, + { + DistMap { + distr: self, + func, + phantom: ::core::marker::PhantomData, + } + } +} + +impl<'a, T, D: Distribution> Distribution for &'a D { + fn sample(&self, rng: &mut R) -> T { + (*self).sample(rng) + } +} + +/// An iterator that generates random values of `T` with distribution `D`, +/// using `R` as the source of randomness. +/// +/// This `struct` is created by the [`sample_iter`] method on [`Distribution`]. +/// See its documentation for more. +/// +/// [`sample_iter`]: Distribution::sample_iter +#[derive(Debug)] +pub struct DistIter { + distr: D, + rng: R, + phantom: ::core::marker::PhantomData, +} + +impl Iterator for DistIter +where + D: Distribution, + R: Rng, +{ + type Item = T; + + #[inline(always)] + fn next(&mut self) -> Option { + // Here, self.rng may be a reference, but we must take &mut anyway. + // Even if sample could take an R: Rng by value, we would need to do this + // since Rng is not copyable and we cannot enforce that this is "reborrowable". + Some(self.distr.sample(&mut self.rng)) + } + + fn size_hint(&self) -> (usize, Option) { + (usize::max_value(), None) + } +} + +impl iter::FusedIterator for DistIter +where + D: Distribution, + R: Rng, +{ +} + +#[cfg(features = "nightly")] +impl iter::TrustedLen for DistIter +where + D: Distribution, + R: Rng, +{ +} + +/// A distribution of values of type `S` derived from the distribution `D` +/// by mapping its output of type `T` through the closure `F`. +/// +/// This `struct` is created by the [`Distribution::map`] method. +/// See its documentation for more. +#[derive(Debug)] +pub struct DistMap { + distr: D, + func: F, + phantom: ::core::marker::PhantomData S>, +} + +impl Distribution for DistMap +where + D: Distribution, + F: Fn(T) -> S, +{ + fn sample(&self, rng: &mut R) -> S { + (self.func)(self.distr.sample(rng)) + } +} + +#[cfg(test)] +mod tests { + use crate::distributions::{Distribution, Uniform}; + use crate::Rng; + + #[test] + fn test_distributions_iter() { + use crate::distributions::Open01; + let mut rng = crate::test::rng(210); + let distr = Open01; + let mut iter = Distribution::::sample_iter(distr, &mut rng); + let mut sum: f32 = 0.; + for _ in 0..100 { + sum += iter.next().unwrap(); + } + assert!(0. < sum && sum < 100.); + } + + #[test] + fn test_distributions_map() { + let dist = Uniform::new_inclusive(0, 5).map(|val| val + 15); + + let mut rng = crate::test::rng(212); + let val = dist.sample(&mut rng); + assert!(val >= 15 && val <= 20); + } + + #[test] + fn test_make_an_iter() { + fn ten_dice_rolls_other_than_five( + rng: &mut R, + ) -> impl Iterator + '_ { + Uniform::new_inclusive(1, 6) + .sample_iter(rng) + .filter(|x| *x != 5) + .take(10) + } + + let mut rng = crate::test::rng(211); + let mut count = 0; + for val in ten_dice_rolls_other_than_five(&mut rng) { + assert!((1..=6).contains(&val) && val != 5); + count += 1; + } + assert_eq!(count, 10); + } +} diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index d1ae30c9402..ef93fd8665a 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -93,22 +93,21 @@ //! [`rand_distr`]: https://crates.io/crates/rand_distr //! [`statrs`]: https://crates.io/crates/statrs -use crate::Rng; -use core::iter; - -pub use self::bernoulli::{Bernoulli, BernoulliError}; -pub use self::float::{Open01, OpenClosed01}; -pub use self::other::Alphanumeric; -pub use self::slice::Slice; -#[doc(inline)] -pub use self::uniform::Uniform; - +mod bernoulli; +mod distribution; +mod float; +mod integer; +mod other; +mod slice; +mod utils; #[cfg(feature = "alloc")] -pub use self::weighted_index::{WeightedError, WeightedIndex}; +mod weighted_index; -mod bernoulli; +#[doc(hidden)] +pub mod hidden_export { + pub use super::float::IntoFloat; // used by rand_distr +} pub mod uniform; - #[deprecated( since = "0.8.0", note = "use rand::distributions::{WeightedIndex, WeightedError} instead" @@ -116,196 +115,19 @@ pub mod uniform; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod weighted; -#[cfg(feature = "alloc")] -mod weighted_index; -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -mod float; -#[doc(hidden)] -pub mod hidden_export { - pub use super::float::IntoFloat; // used by rand_distr -} -mod integer; -mod other; -mod slice; -mod utils; - -/// Types (distributions) that can be used to create a random instance of `T`. -/// -/// It is possible to sample from a distribution through both the -/// `Distribution` and [`Rng`] traits, via `distr.sample(&mut rng)` and -/// `rng.sample(distr)`. They also both offer the [`sample_iter`] method, which -/// produces an iterator that samples from the distribution. -/// -/// All implementations are expected to be immutable; this has the significant -/// advantage of not needing to consider thread safety, and for most -/// distributions efficient state-less sampling algorithms are available. -/// -/// Implementations are typically expected to be portable with reproducible -/// results when used with a PRNG with fixed seed; see the -/// [portability chapter](https://rust-random.github.io/book/portability.html) -/// of The Rust Rand Book. In some cases this does not apply, e.g. the `usize` -/// type requires different sampling on 32-bit and 64-bit machines. -/// -/// [`sample_iter`]: Distribution::sample_iter -pub trait Distribution { - /// Generate a random value of `T`, using `rng` as the source of randomness. - fn sample(&self, rng: &mut R) -> T; - - /// Create an iterator that generates random values of `T`, using `rng` as - /// the source of randomness. - /// - /// Note that this function takes `self` by value. This works since - /// `Distribution` is impl'd for `&D` where `D: Distribution`, - /// however borrowing is not automatic hence `distr.sample_iter(...)` may - /// need to be replaced with `(&distr).sample_iter(...)` to borrow or - /// `(&*distr).sample_iter(...)` to reborrow an existing reference. - /// - /// # Example - /// - /// ``` - /// use rand::thread_rng; - /// use rand::distributions::{Distribution, Alphanumeric, Uniform, Standard}; - /// - /// let mut rng = thread_rng(); - /// - /// // Vec of 16 x f32: - /// let v: Vec = Standard.sample_iter(&mut rng).take(16).collect(); - /// - /// // String: - /// let s: String = Alphanumeric - /// .sample_iter(&mut rng) - /// .take(7) - /// .map(char::from) - /// .collect(); - /// - /// // Dice-rolling: - /// let die_range = Uniform::new_inclusive(1, 6); - /// let mut roll_die = die_range.sample_iter(&mut rng); - /// while roll_die.next().unwrap() != 6 { - /// println!("Not a 6; rolling again!"); - /// } - /// ``` - fn sample_iter(self, rng: R) -> DistIter - where - R: Rng, - Self: Sized, - { - DistIter { - distr: self, - rng, - phantom: ::core::marker::PhantomData, - } - } - - /// Create a distribution of values of 'S' by mapping the output of `Self` - /// through the closure `F` - /// - /// # Example - /// - /// ``` - /// use rand::thread_rng; - /// use rand::distributions::{Distribution, Uniform}; - /// - /// let mut rng = thread_rng(); - /// - /// let die = Uniform::new_inclusive(1, 6); - /// let even_number = die.map(|num| num % 2 == 0); - /// while !even_number.sample(&mut rng) { - /// println!("Still odd; rolling again!"); - /// } - /// ``` - fn map(self, func: F) -> DistMap - where - F: Fn(T) -> S, - Self: Sized, - { - DistMap { - distr: self, - func, - phantom: ::core::marker::PhantomData, - } - } -} - -impl<'a, T, D: Distribution> Distribution for &'a D { - fn sample(&self, rng: &mut R) -> T { - (*self).sample(rng) - } -} - -/// An iterator that generates random values of `T` with distribution `D`, -/// using `R` as the source of randomness. -/// -/// This `struct` is created by the [`sample_iter`] method on [`Distribution`]. -/// See its documentation for more. -/// -/// [`sample_iter`]: Distribution::sample_iter -#[derive(Debug)] -pub struct DistIter { - distr: D, - rng: R, - phantom: ::core::marker::PhantomData, -} - -impl Iterator for DistIter -where - D: Distribution, - R: Rng, -{ - type Item = T; - - #[inline(always)] - fn next(&mut self) -> Option { - // Here, self.rng may be a reference, but we must take &mut anyway. - // Even if sample could take an R: Rng by value, we would need to do this - // since Rng is not copyable and we cannot enforce that this is "reborrowable". - Some(self.distr.sample(&mut self.rng)) - } - - fn size_hint(&self) -> (usize, Option) { - (usize::max_value(), None) - } -} - -impl iter::FusedIterator for DistIter -where - D: Distribution, - R: Rng, -{ -} - -#[cfg(features = "nightly")] -impl iter::TrustedLen for DistIter -where - D: Distribution, - R: Rng, -{ -} - -/// A distribution of values of type `S` derived from the distribution `D` -/// by mapping its output of type `T` through the closure `F`. -/// -/// This `struct` is created by the [`Distribution::map`] method. -/// See its documentation for more. -#[derive(Debug)] -pub struct DistMap { - distr: D, - func: F, - phantom: ::core::marker::PhantomData S>, -} +pub use self::bernoulli::{Bernoulli, BernoulliError}; +pub use self::distribution::{Distribution, DistIter, DistMap}; +pub use self::float::{Open01, OpenClosed01}; +pub use self::other::Alphanumeric; +pub use self::slice::Slice; +#[doc(inline)] +pub use self::uniform::Uniform; +#[cfg(feature = "alloc")] +pub use self::weighted_index::{WeightedError, WeightedIndex}; -impl Distribution for DistMap -where - D: Distribution, - F: Fn(T) -> S, -{ - fn sample(&self, rng: &mut R) -> S { - (self.func)(self.distr.sample(rng)) - } -} +#[allow(unused)] +use crate::Rng; /// A generic random value distribution, implemented for many primitive types. /// Usually generates values with a numerically uniform distribution, and with a @@ -390,53 +212,5 @@ where /// /// [`Uniform`]: uniform::Uniform #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Standard; - -#[cfg(test)] -mod tests { - use super::{Distribution, Uniform}; - use crate::Rng; - - #[test] - fn test_distributions_iter() { - use crate::distributions::Open01; - let mut rng = crate::test::rng(210); - let distr = Open01; - let mut iter = Distribution::::sample_iter(distr, &mut rng); - let mut sum: f32 = 0.; - for _ in 0..100 { - sum += iter.next().unwrap(); - } - assert!(0. < sum && sum < 100.); - } - - #[test] - fn test_distributions_map() { - let dist = Uniform::new_inclusive(0, 5).map(|val| val + 15); - - let mut rng = crate::test::rng(212); - let val = dist.sample(&mut rng); - assert!(val >= 15 && val <= 20); - } - - #[test] - fn test_make_an_iter() { - fn ten_dice_rolls_other_than_five( - rng: &mut R, - ) -> impl Iterator + '_ { - Uniform::new_inclusive(1, 6) - .sample_iter(rng) - .filter(|x| *x != 5) - .take(10) - } - - let mut rng = crate::test::rng(211); - let mut count = 0; - for val in ten_dice_rolls_other_than_five(&mut rng) { - assert!((1..=6).contains(&val) && val != 5); - count += 1; - } - assert_eq!(count, 10); - } -} From b4c1d664097a7221eea0ed4b9e375bf846b83608 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Jun 2021 18:40:43 +0100 Subject: [PATCH 095/443] Add DistString --- src/distributions/distribution.rs | 38 ++++++++++++++++++++++++++++++- src/distributions/mod.rs | 2 ++ src/distributions/other.rs | 25 ++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index 412c91f736e..e7f7677a85a 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -11,6 +11,8 @@ use crate::Rng; use core::iter; +#[cfg(feature = "alloc")] +use alloc::string::String; /// Types (distributions) that can be used to create a random instance of `T`. /// @@ -187,9 +189,27 @@ where } } +/// `String` sampler +/// +/// Sampling a `String` of random characters is not quite the same as collecting +/// a sequence of chars. This trait contains some helpers. +#[cfg(feature = "alloc")] +pub trait DistString { + /// Append `len` random chars to `string` + fn append_string(&self, rng: &mut R, string: &mut String, len: usize); + + /// Generate a `String` of `len` random chars + #[inline] + fn sample_string(&self, rng: &mut R, len: usize) -> String { + let mut s = String::new(); + self.append_string(rng, &mut s, len); + s + } +} + #[cfg(test)] mod tests { - use crate::distributions::{Distribution, Uniform}; + use crate::distributions::{Alphanumeric, Distribution, Standard, Uniform}; use crate::Rng; #[test] @@ -233,4 +253,20 @@ mod tests { } assert_eq!(count, 10); } + + #[test] + #[cfg(feature = "alloc")] + fn test_dist_string() { + use core::str; + use crate::distributions::DistString; + let mut rng = crate::test::rng(213); + + let s1 = Alphanumeric.sample_string(&mut rng, 20); + assert_eq!(s1.len(), 20); + assert_eq!(str::from_utf8(s1.as_bytes()), Ok(s1.as_str())); + + let s2 = Standard.sample_string(&mut rng, 20); + assert_eq!(s2.chars().count(), 20); + assert_eq!(str::from_utf8(s2.as_bytes()), Ok(s2.as_str())); + } } diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index ef93fd8665a..e3086680b71 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -118,6 +118,8 @@ pub mod weighted; pub use self::bernoulli::{Bernoulli, BernoulliError}; pub use self::distribution::{Distribution, DistIter, DistMap}; +#[cfg(feature = "alloc")] +pub use self::distribution::DistString; pub use self::float::{Open01, OpenClosed01}; pub use self::other::Alphanumeric; pub use self::slice::Slice; diff --git a/src/distributions/other.rs b/src/distributions/other.rs index d1f060f483d..2e932703a6d 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -10,8 +10,12 @@ use core::char; use core::num::Wrapping; +#[cfg(feature = "alloc")] +use alloc::string::String; use crate::distributions::{Distribution, Standard, Uniform}; +#[cfg(feature = "alloc")] +use crate::distributions::DistString; use crate::Rng; #[cfg(feature = "serde1")] @@ -85,6 +89,16 @@ impl Distribution for Standard { } } +/// Note: the `String` is potentially left with excess capacity; optionally the +/// user may call `string.shrink_to_fit()` afterwards. +#[cfg(feature = "alloc")] +impl DistString for Standard { + fn append_string(&self, rng: &mut R, s: &mut String, len: usize) { + s.reserve(s.len() + 4 * len); + s.extend(Distribution::::sample_iter(self, rng).take(len)); + } +} + impl Distribution for Alphanumeric { fn sample(&self, rng: &mut R) -> u8 { const RANGE: u32 = 26 + 26 + 10; @@ -104,6 +118,17 @@ impl Distribution for Alphanumeric { } } +#[cfg(feature = "alloc")] +impl DistString for Alphanumeric { + fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { + unsafe { + let v = string.as_mut_vec(); + v.reserve(v.len() + len); + v.extend(self.sample_iter(rng).take(len)); + } + } +} + impl Distribution for Standard { #[inline] fn sample(&self, rng: &mut R) -> bool { From fa17d1c1a292e6bb95743885c839a8a902e89e81 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 8 Jun 2021 08:16:07 +0100 Subject: [PATCH 096/443] Add comment to append_string for Standard --- src/distributions/other.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 2e932703a6d..7ce95ec342e 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -94,6 +94,9 @@ impl Distribution for Standard { #[cfg(feature = "alloc")] impl DistString for Standard { fn append_string(&self, rng: &mut R, s: &mut String, len: usize) { + // A char is encoded with at most four bytes, thus this reservation is + // guaranteed to be sufficient. We do not shrink_to_fit afterwards so + // that repeated usage on the same `String` buffer does not reallocate. s.reserve(s.len() + 4 * len); s.extend(Distribution::::sample_iter(self, rng).take(len)); } From 81f1af8f4f8dcd1241083402ba9c85a079826a2f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 9 Jun 2021 08:52:28 +0100 Subject: [PATCH 097/443] Correct usage of reserve --- src/distributions/other.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 7ce95ec342e..71fb267f8b5 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -97,7 +97,7 @@ impl DistString for Standard { // A char is encoded with at most four bytes, thus this reservation is // guaranteed to be sufficient. We do not shrink_to_fit afterwards so // that repeated usage on the same `String` buffer does not reallocate. - s.reserve(s.len() + 4 * len); + s.reserve(4 * len); s.extend(Distribution::::sample_iter(self, rng).take(len)); } } @@ -126,7 +126,6 @@ impl DistString for Alphanumeric { fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { unsafe { let v = string.as_mut_vec(); - v.reserve(v.len() + len); v.extend(self.sample_iter(rng).take(len)); } } From 1bfc53d57f07af81e193c9ac4156620c3fd50475 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 14 Jun 2021 10:04:59 +0100 Subject: [PATCH 098/443] Update changelogs and bump version numbers --- CHANGELOG.md | 8 ++++---- Cargo.toml | 2 +- rand_core/CHANGELOG.md | 5 +++++ rand_core/Cargo.toml | 2 +- rand_distr/CHANGELOG.md | 4 +++- rand_distr/Cargo.toml | 2 +- rand_hc/CHANGELOG.md | 3 +++ rand_hc/Cargo.toml | 2 +- rand_pcg/CHANGELOG.md | 4 ++++ rand_pcg/Cargo.toml | 2 +- 10 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8baa39e002..2c7387ab04d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,19 +8,19 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. -## [Unreleased] +## [0.8.4] - 2021-06-15 ### Additions - Use const-generics to support arrays of all sizes (#1104) - Implement `Clone` and `Copy` for `Alphanumeric` (#1126) - Add `Distribution::map` to derive a distribution using a closure (#1129) +- Add `Slice` distribution (#1107) +- Add `DistString` trait with impls for `Standard` and `Alphanumeric` (#1133) ### Other - Reorder asserts in `Uniform` float distributions for easier debugging of non-finite arguments (#1094, #1108) - Add range overflow check in `Uniform` float distributions (#1108) - -### Distributions -- Add slice distribution (#1107) +- Deprecate `rngs::adapter::ReadRng` (#1130) ## [0.8.3] - 2021-01-25 ### Fixes diff --git a/Cargo.toml b/Cargo.toml index 7111e48f6da..5ce312342cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.8.3" +version = "0.8.4" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index 23d1fa5a1d3..82c830086cd 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.3] - 2021-06-15 +### Changed +- Improved bound for `serde` impls on `BlockRng` (#1130) +- Minor doc additions (#1118) + ## [0.6.2] - 2021-02-12 ### Fixed - Fixed assertions in `le::read_u32_into` and `le::read_u64_into` which could diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index d460757d21d..6604bc5a8b1 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.6.2" +version = "0.6.3" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 346be3c26bb..27fc9a8ddd3 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,12 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [0.4.1] - 2021-06-15 +- Empirically test PDF of normal distribution (#1121) - Correctly document `no_std` support (#1100) - Add `std_math` feature to prefer `std` over `libm` for floating point math (#1100) - Add mean and std_dev accessors to Normal (#1114) - Make sure all distributions and their error types implement `Error`, `Display`, `Clone`, `Copy`, `PartialEq` and `Eq` as appropriate (#1126) +- Port benchmarks to use Criterion crate (#1116) ## [0.4.0] - 2020-12-18 - Bump `rand` to v0.8.0 diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 38f5adfb820..7bbc5c4d400 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.4.0" +version = "0.4.1" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_hc/CHANGELOG.md b/rand_hc/CHANGELOG.md index ad9fe4dfb40..868679945e0 100644 --- a/rand_hc/CHANGELOG.md +++ b/rand_hc/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.1] - 2021-06-15 +- Adjust crate links + ## [0.3.0] - 2020-12-08 - Bump `rand_core` version to 0.6.0 - Bump MSRV to 1.36 (#1011) diff --git a/rand_hc/Cargo.toml b/rand_hc/Cargo.toml index 403f9f0fb1f..28da55aaeca 100644 --- a/rand_hc/Cargo.toml +++ b/rand_hc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_hc" -version = "0.3.0" +version = "0.3.1" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index 7c929789e11..7464b301001 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.1] - 2021-06-15 +- Add `advance` methods to RNGs (#1111) +- Document dependencies between streams (#1122) + ## [0.3.0] - 2020-12-08 - Bump `rand_core` version to 0.6.0 - Bump MSRV to 1.36 (#1011) diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 4a3f90eff94..8ef7a3b5052 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_pcg" -version = "0.3.0" +version = "0.3.1" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From 277e5b0e9dc0dd0d36cd04f84043321833e61950 Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Tue, 15 Jun 2021 22:27:37 +0200 Subject: [PATCH 099/443] fix some typos --- CHANGELOG.md | 2 +- README.md | 2 +- SECURITY.md | 2 +- rand_core/src/block.rs | 2 +- rand_distr/README.md | 2 +- rand_distr/src/binomial.rs | 4 ++-- rand_distr/src/hypergeometric.rs | 2 +- rand_distr/src/poisson.rs | 2 +- rand_distr/src/utils.rs | 8 ++++---- rand_hc/src/hc128.rs | 4 ++-- rand_pcg/src/pcg128.rs | 2 +- rand_pcg/src/pcg64.rs | 2 +- src/distributions/bernoulli.rs | 2 +- src/distributions/float.rs | 2 +- src/distributions/uniform.rs | 2 +- src/rngs/thread.rs | 4 ++-- 16 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c7387ab04d..b37ca35a05f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -678,4 +678,4 @@ when updating from `rand 0.7.0` without also updating `rand_core`. ## [0.10-pre] - 2014-03-02 ### Added -- Seperate `rand` out of the standard library +- Separate `rand` out of the standard library diff --git a/README.md b/README.md index 355e53bdac9..2f313e39ca9 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ unavailable. The WASM target `wasm32-unknown-unknown` is not *automatically* supported by `rand` or `getrandom`. To solve this, either use a different target such as -`wasm32-wasi` or add a direct dependancy on `getrandom` with the `js` feature +`wasm32-wasi` or add a direct dependency on `getrandom` with the `js` feature (if the target supports JavaScript). See [getrandom#WebAssembly support](https://docs.rs/getrandom/latest/getrandom/#webassembly-support). diff --git a/SECURITY.md b/SECURITY.md index 0da2bf0fed6..bad366c1fd8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -8,7 +8,7 @@ No binding guarantees can be provided. ## Security premises Rand provides the trait `rand_core::CryptoRng` aka `rand::CryptoRng` as a marker -trait. Generators implementating `RngCore` *and* `CryptoRng`, and given the +trait. Generators implementing `RngCore` *and* `CryptoRng`, and given the additional constraints that: - Instances of seedable RNGs (those implementing `SeedableRng`) are diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index a54cadfed7d..5f48bfaf546 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -95,7 +95,7 @@ pub trait BlockRngCore { /// [`fill_bytes`] / [`try_fill_bytes`] is called on a large array. These methods /// also handle the bookkeeping of when to generate a new batch of values. /// -/// No whole generated `u32` values are thown away and all values are consumed +/// No whole generated `u32` values are thrown away and all values are consumed /// in-order. [`next_u32`] simply takes the next available `u32` value. /// [`next_u64`] is implemented by combining two `u32` values, least /// significant first. [`fill_bytes`] and [`try_fill_bytes`] consume a whole diff --git a/rand_distr/README.md b/rand_distr/README.md index b0118426011..35f5dcaa1a8 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -28,7 +28,7 @@ for some distributions (in particular, `Dirichlet` and `WeightedAliasIndex`). The floating point functions from `num_traits` and `libm` are used to support `no_std` environments and ensure reproducibility. If the floating point -functions from `std` are prefered, which may provide better accuracy and +functions from `std` are preferred, which may provide better accuracy and performance but may produce different random values, the `std_math` feature can be enabled. diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index f4fbbfabaa3..a701e6bb684 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -105,7 +105,7 @@ impl Distribution for Binomial { // random variate generation. Commun. ACM 31, 2 (February 1988), // 216-222. http://dx.doi.org/10.1145/42372.42381 - // Threshold for prefering the BINV algorithm. The paper suggests 10, + // Threshold for preferring the BINV algorithm. The paper suggests 10, // Ranlib uses 30, and GSL uses 14. const BINV_THRESHOLD: f64 = 10.; @@ -242,7 +242,7 @@ impl Distribution for Binomial { } } - // Step 5.2: Squeezing. Check the value of ln(v) againts upper and + // Step 5.2: Squeezing. Check the value of ln(v) against upper and // lower bound of ln(f(y)). let k = k as f64; let rho = (k / npq) * ((k * (k / 3. + 0.625) + 1. / 6.) / npq + 0.5); diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 507d7bb07f5..7a5eedc3600 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -29,7 +29,7 @@ enum SamplingMethod { /// `f(k) = binomial(K, k) * binomial(N-K, n-k) / binomial(N, n)`, /// where `binomial(a, b) = a! / (b! * (a - b)!)`. /// -/// The [binomial distribution](crate::Binomial) is the analagous distribution +/// The [binomial distribution](crate::Binomial) is the analogous distribution /// for sampling with replacement. It is a good approximation when the population /// size is much larger than the sample size. /// diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index dc21ef3e7ce..cf20bce5b5d 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -109,7 +109,7 @@ where F: Float + FloatConst, Standard: Distribution loop { // draw from the Cauchy distribution comp_dev = rng.sample(cauchy); - // shift the peak of the comparison ditribution + // shift the peak of the comparison distribution result = self.sqrt_2lambda * comp_dev + self.lambda; // repeat the drawing until we are in the range of possible values if result >= F::zero() { diff --git a/rand_distr/src/utils.rs b/rand_distr/src/utils.rs index 878faf2072b..f097bb45780 100644 --- a/rand_distr/src/utils.rs +++ b/rand_distr/src/utils.rs @@ -91,14 +91,14 @@ where let i = bits as usize & 0xff; let u = if symmetric { - // Convert to a value in the range [2,4) and substract to get [-1,1) + // Convert to a value in the range [2,4) and subtract to get [-1,1) // We can't convert to an open range directly, that would require - // substracting `3.0 - EPSILON`, which is not representable. + // subtracting `3.0 - EPSILON`, which is not representable. // It is possible with an extra step, but an open range does not - // seem neccesary for the ziggurat algorithm anyway. + // seem necessary for the ziggurat algorithm anyway. (bits >> 12).into_float_with_exponent(1) - 3.0 } else { - // Convert to a value in the range [1,2) and substract to get (0,1) + // Convert to a value in the range [1,2) and subtract to get (0,1) (bits >> 12).into_float_with_exponent(0) - (1.0 - core::f64::EPSILON / 2.0) }; let x = u * x_tab[i]; diff --git a/rand_hc/src/hc128.rs b/rand_hc/src/hc128.rs index cedd2cb6ba8..c2af7506a08 100644 --- a/rand_hc/src/hc128.rs +++ b/rand_hc/src/hc128.rs @@ -142,7 +142,7 @@ impl BlockRngCore for Hc128Core { let dd = (cc + 16) % 512; let ee = cc.wrapping_sub(16) % 512; // These asserts let the compiler optimize out the bounds checks. - // Some of them may be superflous, and that's fine: + // Some of them may be superfluous, and that's fine: // they'll be optimized out if that's the case. assert!(ee + 15 < 512); assert!(cc + 15 < 512); @@ -238,7 +238,7 @@ impl Hc128Core { let dd = (cc + 16) % 512; let ee = cc.wrapping_sub(16) % 512; // These asserts let the compiler optimize out the bounds checks. - // Some of them may be superflous, and that's fine: + // Some of them may be superfluous, and that's fine: // they'll be optimized out if that's the case. assert!(ee + 15 < 512); assert!(cc + 15 < 512); diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index f81b7fa8390..0cbb96a42b3 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -93,7 +93,7 @@ impl Lcg128Xsl64 { #[inline] fn from_state_incr(state: u128, increment: u128) -> Self { let mut pcg = Lcg128Xsl64 { state, increment }; - // Move away from inital value: + // Move away from initial value: pcg.state = pcg.state.wrapping_add(pcg.increment); pcg.step(); pcg diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index 602d7bb37fa..5ab945a0f01 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -94,7 +94,7 @@ impl Lcg64Xsh32 { #[inline] fn from_state_incr(state: u64, increment: u64) -> Self { let mut pcg = Lcg64Xsh32 { state, increment }; - // Move away from inital value: + // Move away from initial value: pcg.state = pcg.state.wrapping_add(pcg.increment); pcg.step(); pcg diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index d54d5992c48..bf0d5e5eeb9 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -49,7 +49,7 @@ pub struct Bernoulli { // `f64` only has 53 bits of precision, and the next largest value of `p` will // result in `2^64 - 2048`. // -// Also there is a 100% theoretical concern: if someone consistenly wants to +// Also there is a 100% theoretical concern: if someone consistently wants to // generate `true` using the Bernoulli distribution (i.e. by using a probability // of `1.0`), just using `u64::MAX` is not enough. On average it would return // false once every 2^64 iterations. Some people apparently care about this diff --git a/src/distributions/float.rs b/src/distributions/float.rs index 733a40394dd..ce5946f7f01 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -78,7 +78,7 @@ pub struct Open01; pub trait IntoFloat { type F; - /// Helper method to combine the fraction and a contant exponent into a + /// Helper method to combine the fraction and a constant exponent into a /// float. /// /// Only the least significant bits of `self` may be set, 23 for `f32` and diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index fb898370329..516a58c7072 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -158,7 +158,7 @@ use serde::{Serialize, Deserialize}; /// println!("{}", sum); /// ``` /// -/// For a single sample, [`Rng::gen_range`] may be prefered: +/// For a single sample, [`Rng::gen_range`] may be preferred: /// /// ``` /// use rand::Rng; diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 552851f1ec3..13511e3ed2a 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -33,7 +33,7 @@ use crate::{CryptoRng, Error, RngCore, SeedableRng}; // Number of generated bytes after which to reseed `ThreadRng`. -// According to benchmarks, reseeding has a noticable impact with thresholds +// According to benchmarks, reseeding has a noticeable impact with thresholds // of 32 kB and less. We choose 64 kB to avoid significant overhead. const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; @@ -59,7 +59,7 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] #[derive(Clone, Debug)] pub struct ThreadRng { - // Rc is explictly !Send and !Sync + // Rc is explicitly !Send and !Sync rng: Rc>>, } From 0360aa9c7533203416ae6f322be61676a205dfe7 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 13 Jun 2021 19:47:41 +0200 Subject: [PATCH 100/443] rand_distr: Add Zipf distribution --- rand_distr/src/lib.rs | 4 +- rand_distr/src/zipf.rs | 143 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 rand_distr/src/zipf.rs diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index f44292724ab..4475c1012a9 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -75,6 +75,7 @@ //! - Misc. distributions //! - [`InverseGaussian`] distribution //! - [`NormalInverseGaussian`] distribution +//! - [`Zipf`] distribution #[cfg(feature = "alloc")] extern crate alloc; @@ -115,6 +116,7 @@ pub use self::unit_circle::UnitCircle; pub use self::unit_disc::UnitDisc; pub use self::unit_sphere::UnitSphere; pub use self::weibull::{Error as WeibullError, Weibull}; +pub use self::zipf::{Error as ZipfError, Zipf}; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use rand::distributions::{WeightedError, WeightedIndex}; @@ -198,4 +200,4 @@ mod unit_sphere; mod utils; mod weibull; mod ziggurat_tables; - +mod zipf; diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs new file mode 100644 index 00000000000..4fd2270bc24 --- /dev/null +++ b/rand_distr/src/zipf.rs @@ -0,0 +1,143 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Zipf distribution. + +use num_traits::Float; +use crate::{Distribution, Standard}; +use rand::Rng; +use core::fmt; + +/// Samples floating-point numbers according to the Zipf distribution. +/// +/// The Zipf distribution (also known as the zeta distribution) is a continuous +/// probability distribution that satisfies Zipf’s law: the frequency of an item +/// is inversely proportional to its rank in a frequency table. +/// +/// # Example +/// ``` +/// use rand::prelude::*; +/// use rand_distr::Zipf; +/// +/// let val: f64 = thread_rng().sample(Zipf::new(1.5).unwrap()); +/// println!("{}", val); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Zipf +where F: Float, Standard: Distribution +{ + a_minus_1: F, + b: F, +} + +/// Error type returned from `Zipf::new`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// `a <= 1` or `nan`. + ATooSmall, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::ATooSmall => "a <= 0 or is NaN in Zipf distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + +impl Zipf +where F: Float, Standard: Distribution +{ + /// Construct a new `Zipf` distribution with given `a` parameter. + pub fn new(a: F) -> Result, Error> { + if !(a > F::one()) { + return Err(Error::ATooSmall); + } + let a_minus_1 = a - F::one(); + let two = F::one() + F::one(); + Ok(Zipf { + a_minus_1, + b: two.powf(a_minus_1), + }) + } +} + +impl Distribution for Zipf +where F: Float, Standard: Distribution +{ + fn sample(&self, rng: &mut R) -> F { + // This is based on the numpy implementation. + loop { + let u = F::one() - rng.sample(Standard); + let v = rng.sample(Standard); + let x = u.powf(-F::one() / self.a_minus_1).floor(); + + if x < F::one() { + continue; + } + + let t = (F::one() + F::one() / x).powf(self.a_minus_1); + if v * x * (t - F::one()) / (self.b - F::one()) <= t / self.b { + return x; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn invalid() { + Zipf::new(1.).unwrap(); + } + + #[test] + #[should_panic] + fn nan() { + Zipf::new(core::f64::NAN).unwrap(); + } + + #[test] + fn sample() { + let a = 2.0; + let d = Zipf::new(a).unwrap(); + let mut rng = crate::test::rng(1); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 1.); + } + } + + #[test] + fn value_stability() { + fn test_samples>( + distr: D, zero: F, expected: &[F], + ) { + let mut rng = crate::test::rng(213); + let mut buf = [zero; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + test_samples(Zipf::new(1.5).unwrap(), 0f32, &[ + 605.0, 1.0, 8.0, 15.0, + ]); + test_samples(Zipf::new(2.0).unwrap(), 0f64, &[ + 1.0, 2.0, 4.0, 1.0, + ]); + } +} From 1e1e768f0e69cdeac448c9b0b287b3f12aae289b Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 14 Jun 2021 01:25:17 +0200 Subject: [PATCH 101/443] Update changelog --- rand_distr/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 27fc9a8ddd3..887852b0a91 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +- New `Zipf` distribution (#1136) + ## [0.4.1] - 2021-06-15 - Empirically test PDF of normal distribution (#1121) - Correctly document `no_std` support (#1100) From a57247d9a2fe61067c6be44773a75772f26953ce Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 15 Jun 2021 12:06:02 +0200 Subject: [PATCH 102/443] Zipf: Use `OpenClosed01` --- rand_distr/src/zipf.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 4fd2270bc24..46d9027dad1 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -10,7 +10,7 @@ use num_traits::Float; use crate::{Distribution, Standard}; -use rand::Rng; +use rand::{Rng, distributions::OpenClosed01}; use core::fmt; /// Samples floating-point numbers according to the Zipf distribution. @@ -29,7 +29,7 @@ use core::fmt; /// ``` #[derive(Clone, Copy, Debug)] pub struct Zipf -where F: Float, Standard: Distribution +where F: Float, Standard: Distribution, OpenClosed01: Distribution { a_minus_1: F, b: F, @@ -55,7 +55,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl Zipf -where F: Float, Standard: Distribution +where F: Float, Standard: Distribution, OpenClosed01: Distribution { /// Construct a new `Zipf` distribution with given `a` parameter. pub fn new(a: F) -> Result, Error> { @@ -72,12 +72,12 @@ where F: Float, Standard: Distribution } impl Distribution for Zipf -where F: Float, Standard: Distribution +where F: Float, Standard: Distribution, OpenClosed01: Distribution { fn sample(&self, rng: &mut R) -> F { // This is based on the numpy implementation. loop { - let u = F::one() - rng.sample(Standard); + let u = rng.sample(OpenClosed01); let v = rng.sample(Standard); let x = u.powf(-F::one() / self.a_minus_1).floor(); From 718e71b826a8a4e8c568a4069d5c63cb1e8c33c7 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 15 Jun 2021 19:03:57 +0200 Subject: [PATCH 103/443] Zipf: Add benchmark --- rand_distr/benches/src/distributions.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rand_distr/benches/src/distributions.rs b/rand_distr/benches/src/distributions.rs index 621f351868c..2b3ff560954 100644 --- a/rand_distr/benches/src/distributions.rs +++ b/rand_distr/benches/src/distributions.rs @@ -195,6 +195,11 @@ fn bench(c: &mut Criterion) { distr_float!(g, "poisson", f64, Poisson::new(4.0).unwrap()); } + { + let mut g = c.benchmark_group("zipf"); + distr_float!(g, "zipf", f64, Zipf::new(1.5).unwrap()); + } + { let mut g = c.benchmark_group("bernoulli"); distr!(g, "bernoulli", bool, Bernoulli::new(0.18).unwrap()); From c2ecf1babb77d3cef5258d97e8a7664a74cf54de Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 15 Jun 2021 19:06:37 +0200 Subject: [PATCH 104/443] Fix value stability tests --- rand_distr/src/zipf.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 46d9027dad1..2990c2b4bec 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -134,10 +134,10 @@ mod tests { } test_samples(Zipf::new(1.5).unwrap(), 0f32, &[ - 605.0, 1.0, 8.0, 15.0, + 1.0, 2.0, 1.0, 1.0, ]); test_samples(Zipf::new(2.0).unwrap(), 0f64, &[ - 1.0, 2.0, 4.0, 1.0, + 2.0, 1.0, 1.0, 1.0, ]); } } From 6c27184f22ae1bbde728f3865315078d04592955 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 00:13:45 +0200 Subject: [PATCH 105/443] Rename `Zipf` to `Zeta` This follows the naming convention on Wikipedia. --- rand_distr/CHANGELOG.md | 2 +- rand_distr/benches/src/distributions.rs | 2 +- rand_distr/src/lib.rs | 4 +-- rand_distr/src/zipf.rs | 40 ++++++++++++------------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 887852b0a91..b4b09f9404e 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased -- New `Zipf` distribution (#1136) +- New `Zeta` distribution (#1136) ## [0.4.1] - 2021-06-15 - Empirically test PDF of normal distribution (#1121) diff --git a/rand_distr/benches/src/distributions.rs b/rand_distr/benches/src/distributions.rs index 2b3ff560954..98a1fca0ac7 100644 --- a/rand_distr/benches/src/distributions.rs +++ b/rand_distr/benches/src/distributions.rs @@ -197,7 +197,7 @@ fn bench(c: &mut Criterion) { { let mut g = c.benchmark_group("zipf"); - distr_float!(g, "zipf", f64, Zipf::new(1.5).unwrap()); + distr_float!(g, "zipf", f64, Zeta::new(1.5).unwrap()); } { diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 4475c1012a9..750dffb0e3e 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -75,7 +75,7 @@ //! - Misc. distributions //! - [`InverseGaussian`] distribution //! - [`NormalInverseGaussian`] distribution -//! - [`Zipf`] distribution +//! - [`Zeta`] distribution #[cfg(feature = "alloc")] extern crate alloc; @@ -116,7 +116,7 @@ pub use self::unit_circle::UnitCircle; pub use self::unit_disc::UnitDisc; pub use self::unit_sphere::UnitSphere; pub use self::weibull::{Error as WeibullError, Weibull}; -pub use self::zipf::{Error as ZipfError, Zipf}; +pub use self::zipf::{Error as ZetaError, Zeta}; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use rand::distributions::{WeightedError, WeightedIndex}; diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 2990c2b4bec..35f5132c7f8 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -6,36 +6,36 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Zipf distribution. +//! The Zeta and related distributions. use num_traits::Float; use crate::{Distribution, Standard}; use rand::{Rng, distributions::OpenClosed01}; use core::fmt; -/// Samples floating-point numbers according to the Zipf distribution. +/// Samples floating-point numbers according to the zeta distribution. /// -/// The Zipf distribution (also known as the zeta distribution) is a continuous -/// probability distribution that satisfies Zipf’s law: the frequency of an item -/// is inversely proportional to its rank in a frequency table. +/// The zeta distribution is a continuous probability distribution that +/// satisfies Zipf’s law: the frequency of an item is inversely proportional to +/// its rank in a frequency table. It is a limit of the Zipf distribution. /// /// # Example /// ``` /// use rand::prelude::*; -/// use rand_distr::Zipf; +/// use rand_distr::Zeta; /// -/// let val: f64 = thread_rng().sample(Zipf::new(1.5).unwrap()); +/// let val: f64 = thread_rng().sample(Zeta::new(1.5).unwrap()); /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug)] -pub struct Zipf +pub struct Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution { a_minus_1: F, b: F, } -/// Error type returned from `Zipf::new`. +/// Error type returned from `Zeta::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `a <= 1` or `nan`. @@ -45,7 +45,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - Error::ATooSmall => "a <= 0 or is NaN in Zipf distribution", + Error::ATooSmall => "a <= 0 or is NaN in Zeta distribution", }) } } @@ -54,24 +54,24 @@ impl fmt::Display for Error { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} -impl Zipf +impl Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution { - /// Construct a new `Zipf` distribution with given `a` parameter. - pub fn new(a: F) -> Result, Error> { + /// Construct a new `Zeta` distribution with given `a` parameter. + pub fn new(a: F) -> Result, Error> { if !(a > F::one()) { return Err(Error::ATooSmall); } let a_minus_1 = a - F::one(); let two = F::one() + F::one(); - Ok(Zipf { + Ok(Zeta { a_minus_1, b: two.powf(a_minus_1), }) } } -impl Distribution for Zipf +impl Distribution for Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution { fn sample(&self, rng: &mut R) -> F { @@ -100,19 +100,19 @@ mod tests { #[test] #[should_panic] fn invalid() { - Zipf::new(1.).unwrap(); + Zeta::new(1.).unwrap(); } #[test] #[should_panic] fn nan() { - Zipf::new(core::f64::NAN).unwrap(); + Zeta::new(core::f64::NAN).unwrap(); } #[test] fn sample() { let a = 2.0; - let d = Zipf::new(a).unwrap(); + let d = Zeta::new(a).unwrap(); let mut rng = crate::test::rng(1); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -133,10 +133,10 @@ mod tests { assert_eq!(buf, expected); } - test_samples(Zipf::new(1.5).unwrap(), 0f32, &[ + test_samples(Zeta::new(1.5).unwrap(), 0f32, &[ 1.0, 2.0, 1.0, 1.0, ]); - test_samples(Zipf::new(2.0).unwrap(), 0f64, &[ + test_samples(Zeta::new(2.0).unwrap(), 0f64, &[ 2.0, 1.0, 1.0, 1.0, ]); } From b06c2f61af63a1ccffd56e3b9db5c195bcc58706 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 00:21:26 +0200 Subject: [PATCH 106/443] Don't claim `Zeta` follows Zipf's law It seems like this is not true [1], at least not with the same exponent. [1] https://en.wikipedia.org/wiki/Zeta_distribution --- rand_distr/src/zipf.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 35f5132c7f8..bdb8f9b1b55 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -15,9 +15,7 @@ use core::fmt; /// Samples floating-point numbers according to the zeta distribution. /// -/// The zeta distribution is a continuous probability distribution that -/// satisfies Zipf’s law: the frequency of an item is inversely proportional to -/// its rank in a frequency table. It is a limit of the Zipf distribution. +/// The zeta distribution is a limit of the Zipf distribution. /// /// # Example /// ``` From a07b3218faf42897b018ded19d48164562066bf9 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 01:52:16 +0200 Subject: [PATCH 107/443] rand_distr: Add Zipf (not zeta) distribution --- rand_distr/CHANGELOG.md | 2 +- rand_distr/src/lib.rs | 3 +- rand_distr/src/zipf.rs | 214 +++++++++++++++++++++++++++++++++++----- 3 files changed, 195 insertions(+), 24 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index b4b09f9404e..09ec411a5b8 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased -- New `Zeta` distribution (#1136) +- New `Zeta` and `Zipf` distributions (#1136) ## [0.4.1] - 2021-06-15 - Empirically test PDF of normal distribution (#1121) diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 750dffb0e3e..9d4496b4271 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -76,6 +76,7 @@ //! - [`InverseGaussian`] distribution //! - [`NormalInverseGaussian`] distribution //! - [`Zeta`] distribution +//! - [`Zipf`] distribution #[cfg(feature = "alloc")] extern crate alloc; @@ -116,7 +117,7 @@ pub use self::unit_circle::UnitCircle; pub use self::unit_disc::UnitDisc; pub use self::unit_sphere::UnitSphere; pub use self::weibull::{Error as WeibullError, Weibull}; -pub use self::zipf::{Error as ZetaError, Zeta}; +pub use self::zipf::{ZetaError, Zeta, ZipfError, Zipf}; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use rand::distributions::{WeightedError, WeightedIndex}; diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index bdb8f9b1b55..2733bc6b35b 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -15,7 +15,7 @@ use core::fmt; /// Samples floating-point numbers according to the zeta distribution. /// -/// The zeta distribution is a limit of the Zipf distribution. +/// The zeta distribution is a limit of the [`Zipf`] distribution. /// /// # Example /// ``` @@ -35,30 +35,30 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution /// Error type returned from `Zeta::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Error { +pub enum ZetaError { /// `a <= 1` or `nan`. ATooSmall, } -impl fmt::Display for Error { +impl fmt::Display for ZetaError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - Error::ATooSmall => "a <= 0 or is NaN in Zeta distribution", + ZetaError::ATooSmall => "a <= 1 or is NaN in Zeta distribution", }) } } #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -impl std::error::Error for Error {} +impl std::error::Error for ZetaError {} impl Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution { /// Construct a new `Zeta` distribution with given `a` parameter. - pub fn new(a: F) -> Result, Error> { + pub fn new(a: F) -> Result, ZetaError> { if !(a > F::one()) { - return Err(Error::ATooSmall); + return Err(ZetaError::ATooSmall); } let a_minus_1 = a - F::one(); let two = F::one() + F::one(); @@ -91,24 +91,135 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution } } +/// Samples floating-point numbers according to the Zipf distribution. +/// +/// The samples follow Zipf's law: The frequency of each sample is inversely +/// proportional to a power of its frequency rank. +/// +/// For large `n`, this converges to the [`Zeta`] distribution. +/// +/// For `s = 0`, this becomes a uniform distribution. +/// +/// # Example +/// ``` +/// use rand::prelude::*; +/// use rand_distr::Zipf; +/// +/// let val: f64 = thread_rng().sample(Zipf::new(10, 1.5).unwrap()); +/// println!("{}", val); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Zipf +where F: Float, Standard: Distribution, OpenClosed01: Distribution { + n: F, + s: F, + t: F, +} + +/// Error type returned from `Zipf::new`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ZipfError { + /// `s < 0` or `nan`. + STooSmall, + /// `n < 1`. + NTooSmall, +} + +impl fmt::Display for ZipfError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + ZipfError::STooSmall => "s < 0 or is NaN in Zipf distribution", + ZipfError::NTooSmall => "n < 1 in Zipf distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for ZipfError {} + +impl Zipf +where F: Float, Standard: Distribution, OpenClosed01: Distribution { + /// Construct a new `Zipf` distribution for a set with `n` elements and a + /// frequency rank exponent `s`. + pub fn new(n: u64, s: F) -> Result, ZipfError> { + if !(s >= F::zero()) { + return Err(ZipfError::STooSmall); + } + if n < 1 { + return Err(ZipfError::NTooSmall); + } + let n = F::from(n).unwrap(); + Ok(Zipf { + // FIXME: properly deal with `s == 1` + n, s, t: (n.powf(F::one() - s) - s) / (F::one() - s) + }) + } + + /// Inverse cumulative density function + fn inv_cdf(&self, p: F) -> F { + let one = F::one(); + let pt = p * self.t; + if pt <= one { + pt + } else { + (pt * (one - self.s) + self.s).powf(one / (one - self.s)) + } + } +} + +impl Distribution for Zipf +where F: Float, Standard: Distribution, OpenClosed01: Distribution +{ + fn sample(&self, rng: &mut R) -> F { + let one = F::one(); + loop { + let inv_b = self.inv_cdf(rng.sample(Standard)); + let x = (inv_b + one).floor(); + let numer = x.powf(-self.s); + let denom = if x <= one { + one / self.t + } else { + inv_b.powf(-self.s) / self.t + }; + + let y = rng.sample(Standard); + if y < numer / denom { + return x; + } + } + } +} + #[cfg(test)] mod tests { use super::*; + fn test_samples>( + distr: D, zero: F, expected: &[F], + ) { + let mut rng = crate::test::rng(213); + let mut buf = [zero; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + #[test] #[should_panic] - fn invalid() { + fn zeta_invalid() { Zeta::new(1.).unwrap(); } #[test] #[should_panic] - fn nan() { + fn zeta_nan() { Zeta::new(core::f64::NAN).unwrap(); } #[test] - fn sample() { + fn zeta_sample() { let a = 2.0; let d = Zeta::new(a).unwrap(); let mut rng = crate::test::rng(1); @@ -119,18 +230,7 @@ mod tests { } #[test] - fn value_stability() { - fn test_samples>( - distr: D, zero: F, expected: &[F], - ) { - let mut rng = crate::test::rng(213); - let mut buf = [zero; 4]; - for x in &mut buf { - *x = rng.sample(&distr); - } - assert_eq!(buf, expected); - } - + fn zeta_value_stability() { test_samples(Zeta::new(1.5).unwrap(), 0f32, &[ 1.0, 2.0, 1.0, 1.0, ]); @@ -138,4 +238,74 @@ mod tests { 2.0, 1.0, 1.0, 1.0, ]); } + + #[test] + #[should_panic] + fn zipf_s_too_small() { + Zipf::new(10, -1.).unwrap(); + } + + #[test] + #[should_panic] + fn zipf_n_too_small() { + Zipf::new(0, 1.).unwrap(); + } + + #[test] + #[should_panic] + fn zipf_nan() { + Zipf::new(10, core::f64::NAN).unwrap(); + } + + #[test] + fn zipf_sample() { + let d = Zipf::new(10, 0.5).unwrap(); + let mut rng = crate::test::rng(2); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 1.); + } + } + + #[test] + fn zipf_sample_s_1() { + let d = Zipf::new(10, 1.).unwrap(); + let mut rng = crate::test::rng(2); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 1.); + } + } + + #[test] + fn zipf_sample_s_0() { + let d = Zipf::new(10, 0.).unwrap(); + let mut rng = crate::test::rng(2); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 1.); + } + // TODO: verify that this is a uniform distribution + } + + #[test] + fn zipf_sample_large_n() { + let d = Zipf::new(core::u64::MAX, 1.5).unwrap(); + let mut rng = crate::test::rng(2); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 1.); + } + // TODO: verify that this is a zeta distribution + } + + #[test] + fn zipf_value_stability() { + test_samples(Zipf::new(10, 0.5).unwrap(), 0f32, &[ + 10.0, 2.0, 6.0, 7.0 + ]); + test_samples(Zipf::new(10, 2.0).unwrap(), 0f64, &[ + 1.0, 2.0, 3.0, 2.0 + ]); + } } From 6270248f59ac2f43780febd161442af6d7ef710c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 02:11:17 +0200 Subject: [PATCH 108/443] Zipf: Fix `s = 1` special case Also inline the distribution methods. --- rand_distr/src/zipf.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 2733bc6b35b..cfd07a62f9e 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -142,6 +142,7 @@ impl Zipf where F: Float, Standard: Distribution, OpenClosed01: Distribution { /// Construct a new `Zipf` distribution for a set with `n` elements and a /// frequency rank exponent `s`. + #[inline] pub fn new(n: u64, s: F) -> Result, ZipfError> { if !(s >= F::zero()) { return Err(ZipfError::STooSmall); @@ -149,21 +150,28 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution { if n < 1 { return Err(ZipfError::NTooSmall); } - let n = F::from(n).unwrap(); + let n = F::from(n).unwrap(); // FIXME + let t = if s != F::one() { + (n.powf(F::one() - s) - s) / (F::one() - s) + } else { + F::one() + n.ln() + }; Ok(Zipf { - // FIXME: properly deal with `s == 1` - n, s, t: (n.powf(F::one() - s) - s) / (F::one() - s) + n, s, t }) } /// Inverse cumulative density function + #[inline] fn inv_cdf(&self, p: F) -> F { let one = F::one(); let pt = p * self.t; if pt <= one { pt - } else { + } else if self.s != F::one() { (pt * (one - self.s) + self.s).powf(one / (one - self.s)) + } else { + pt.exp() } } } @@ -171,6 +179,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution { impl Distribution for Zipf where F: Float, Standard: Distribution, OpenClosed01: Distribution { + #[inline] fn sample(&self, rng: &mut R) -> F { let one = F::one(); loop { From 4d67af2604ce2305fc5ca793c9ea9dd1e31ab8e5 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 02:30:46 +0200 Subject: [PATCH 109/443] Zipf: Mention that rounding may occur --- rand_distr/src/zipf.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index cfd07a62f9e..a5e8711c84f 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -142,6 +142,8 @@ impl Zipf where F: Float, Standard: Distribution, OpenClosed01: Distribution { /// Construct a new `Zipf` distribution for a set with `n` elements and a /// frequency rank exponent `s`. + /// + /// For large `n`, rounding may occur to fit the number into the float type. #[inline] pub fn new(n: u64, s: F) -> Result, ZipfError> { if !(s >= F::zero()) { @@ -150,7 +152,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution { if n < 1 { return Err(ZipfError::NTooSmall); } - let n = F::from(n).unwrap(); // FIXME + let n = F::from(n).unwrap(); // This does not fail. let t = if s != F::one() { (n.powf(F::one() - s) - s) / (F::one() - s) } else { From 139e898f0654fa17e3a4948ef1e82067369a87d4 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 02:32:00 +0200 Subject: [PATCH 110/443] Zipf: Simplify trait bounds --- rand_distr/src/zipf.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index a5e8711c84f..ffe5ac354de 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -110,7 +110,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution /// ``` #[derive(Clone, Copy, Debug)] pub struct Zipf -where F: Float, Standard: Distribution, OpenClosed01: Distribution { +where F: Float, Standard: Distribution { n: F, s: F, t: F, @@ -139,7 +139,7 @@ impl fmt::Display for ZipfError { impl std::error::Error for ZipfError {} impl Zipf -where F: Float, Standard: Distribution, OpenClosed01: Distribution { +where F: Float, Standard: Distribution { /// Construct a new `Zipf` distribution for a set with `n` elements and a /// frequency rank exponent `s`. /// @@ -179,7 +179,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution { } impl Distribution for Zipf -where F: Float, Standard: Distribution, OpenClosed01: Distribution +where F: Float, Standard: Distribution { #[inline] fn sample(&self, rng: &mut R) -> F { From f514fd68badcdf72d9249e92157dd26b8e5bef39 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 02:36:29 +0200 Subject: [PATCH 111/443] Zipf: Simplify calculation of ratio This should improve performance slightly. --- rand_distr/src/zipf.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index ffe5ac354de..a0fbd51cff5 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -187,15 +187,13 @@ where F: Float, Standard: Distribution loop { let inv_b = self.inv_cdf(rng.sample(Standard)); let x = (inv_b + one).floor(); - let numer = x.powf(-self.s); - let denom = if x <= one { - one / self.t - } else { - inv_b.powf(-self.s) / self.t + let mut ratio = x.powf(-self.s) * self.t; + if x > one { + ratio = ratio * inv_b.powf(self.s) }; let y = rng.sample(Standard); - if y < numer / denom { + if y < ratio { return x; } } From ccaa4de99020d83f9693686cbacca4032a22f55b Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 02:49:18 +0200 Subject: [PATCH 112/443] Zipf: Update benchmarks --- rand_distr/benches/src/distributions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rand_distr/benches/src/distributions.rs b/rand_distr/benches/src/distributions.rs index 98a1fca0ac7..61c2aa1883c 100644 --- a/rand_distr/benches/src/distributions.rs +++ b/rand_distr/benches/src/distributions.rs @@ -197,7 +197,8 @@ fn bench(c: &mut Criterion) { { let mut g = c.benchmark_group("zipf"); - distr_float!(g, "zipf", f64, Zeta::new(1.5).unwrap()); + distr_float!(g, "zipf", f64, Zipf::new(10, 1.5).unwrap()); + distr_float!(g, "zeta", f64, Zeta::new(1.5).unwrap()); } { From 3cccc64a464dadc8dc6819b508b813184684902c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 02:50:58 +0200 Subject: [PATCH 113/443] Zeta: Inline distribution methods --- rand_distr/src/zipf.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index a0fbd51cff5..41d10f4ec0b 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -56,6 +56,7 @@ impl Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution { /// Construct a new `Zeta` distribution with given `a` parameter. + #[inline] pub fn new(a: F) -> Result, ZetaError> { if !(a > F::one()) { return Err(ZetaError::ATooSmall); @@ -72,6 +73,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution impl Distribution for Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution { + #[inline] fn sample(&self, rng: &mut R) -> F { // This is based on the numpy implementation. loop { From 14d55f86642bb9e4fc306765bbcc18b570ccb45d Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 16 Jun 2021 02:53:10 +0200 Subject: [PATCH 114/443] Group `Zeta` and `Zipf` with rate-related distributions Arguably, the Zipf distribution is related to the Pareto distribution [1]. [1] https://en.wikipedia.org/wiki/Zipf's_law#Related_laws --- rand_distr/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 9d4496b4271..4b9e803a130 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -56,6 +56,8 @@ //! - [`Poisson`] distribution //! - [`Exp`]onential distribution, and [`Exp1`] as a primitive //! - [`Weibull`] distribution +//! - [`Zeta`] distribution +//! - [`Zipf`] distribution //! - Gamma and derived distributions: //! - [`Gamma`] distribution //! - [`ChiSquared`] distribution @@ -75,8 +77,6 @@ //! - Misc. distributions //! - [`InverseGaussian`] distribution //! - [`NormalInverseGaussian`] distribution -//! - [`Zeta`] distribution -//! - [`Zipf`] distribution #[cfg(feature = "alloc")] extern crate alloc; From b27c38372dd1a0aeabf651feb88aff99967be24c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 8 Jul 2021 14:51:55 +0100 Subject: [PATCH 115/443] rand_distr: add serde1 feature --- .github/workflows/test.yml | 4 ++-- rand_distr/Cargo.toml | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a5386b6187a..8355d6b7c23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features --features=alloc,getrandom - name: Test rand_distr run: | - cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml + cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde1 cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --no-default-features cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --no-default-features --features=std,std_math - name: Test rand_pcg @@ -134,7 +134,7 @@ jobs: cross test --no-fail-fast --target ${{ matrix.target }} --features=serde1,log,small_rng cross test --no-fail-fast --target ${{ matrix.target }} --examples cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml - cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml + cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde1 cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde1 cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_hc/Cargo.toml diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 38f5adfb820..74611bee567 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -15,15 +15,17 @@ categories = ["algorithms", "no-std"] edition = "2018" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] -[dependencies] -rand = { path = "..", version = "0.8.0", default-features = false } -num-traits = { version = "0.2", default-features = false, features = ["libm"] } - [features] default = ["std"] std = ["alloc", "rand/std"] alloc = ["rand/alloc"] std_math = ["num-traits/std"] +serde1 = ["serde"] + +[dependencies] +rand = { path = "..", version = "0.8.0", default-features = false } +num-traits = { version = "0.2", default-features = false, features = ["libm"] } +serde = { version = "1.0.103", features = ["derive"], optional = true } [dev-dependencies] rand_pcg = { version = "0.3.0", path = "../rand_pcg" } From b9f0c54a2c8dee333d0218e87601c7a8b37f8bff Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 8 Jul 2021 15:00:32 +0100 Subject: [PATCH 116/443] rand_distr: derive serde support (easy cases) --- rand_distr/src/binomial.rs | 1 + rand_distr/src/cauchy.rs | 1 + rand_distr/src/dirichlet.rs | 1 + rand_distr/src/exponential.rs | 2 ++ rand_distr/src/gamma.rs | 17 +++++++++++++++++ rand_distr/src/geometric.rs | 4 +++- rand_distr/src/hypergeometric.rs | 2 ++ rand_distr/src/inverse_gaussian.rs | 1 + rand_distr/src/normal.rs | 3 +++ rand_distr/src/normal_inverse_gaussian.rs | 1 + rand_distr/src/pareto.rs | 1 + rand_distr/src/pert.rs | 1 + rand_distr/src/poisson.rs | 1 + rand_distr/src/triangular.rs | 1 + rand_distr/src/unit_ball.rs | 1 + rand_distr/src/unit_circle.rs | 1 + rand_distr/src/unit_disc.rs | 1 + rand_distr/src/unit_sphere.rs | 1 + rand_distr/src/weibull.rs | 1 + 19 files changed, 41 insertions(+), 1 deletion(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index f4fbbfabaa3..f3c45953341 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -29,6 +29,7 @@ use core::cmp::Ordering; /// println!("{} is from a binomial distribution", v); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Binomial { /// Number of trials. n: u64, diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 66dc09b3e7b..49b121c41d2 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -32,6 +32,7 @@ use core::fmt; /// println!("{} is from a Cauchy(2, 5) distribution", v); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Cauchy where F: Float + FloatConst, Standard: Distribution { diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 6286e935742..0ffbc40a049 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -33,6 +33,7 @@ use alloc::{boxed::Box, vec, vec::Vec}; /// ``` #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Clone, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Dirichlet where F: Float, diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index e389319d91f..4e33c3cac6e 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -39,6 +39,7 @@ use core::fmt; /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Exp1; impl Distribution for Exp1 { @@ -91,6 +92,7 @@ impl Distribution for Exp1 { /// println!("{} is from a Exp(2) distribution", v); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Exp where F: Float, Exp1: Distribution { diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 45181b9204c..87faf11c893 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -21,6 +21,8 @@ use num_traits::Float; use crate::{Distribution, Exp, Exp1, Open01}; use rand::Rng; use core::fmt; +#[cfg(feature = "serde1")] +use serde::{Serialize, Deserialize}; /// The Gamma distribution `Gamma(shape, scale)` distribution. /// @@ -53,6 +55,7 @@ use core::fmt; /// (September 2000), 363-372. /// DOI:[10.1145/358407.358414](https://doi.acm.org/10.1145/358407.358414) #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Gamma where F: Float, @@ -89,6 +92,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum GammaRepr where F: Float, @@ -116,6 +120,7 @@ where /// See `Gamma` for sampling from a Gamma distribution with general /// shape parameters. #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct GammaSmallShape where F: Float, @@ -131,6 +136,7 @@ where /// See `Gamma` for sampling from a Gamma distribution with general /// shape parameters. #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct GammaLargeShape where F: Float, @@ -275,6 +281,7 @@ where /// println!("{} is from a χ²(11) distribution", v) /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct ChiSquared where F: Float, @@ -287,6 +294,7 @@ where /// Error type returned from `ChiSquared::new` and `StudentT::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum ChiSquaredError { /// `0.5 * k <= 0` or `nan`. DoFTooSmall, @@ -307,6 +315,7 @@ impl fmt::Display for ChiSquaredError { impl std::error::Error for ChiSquaredError {} #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum ChiSquaredRepr where F: Float, @@ -377,6 +386,7 @@ where /// println!("{} is from an F(2, 32) distribution", v) /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct FisherF where F: Float, @@ -393,6 +403,7 @@ where /// Error type returned from `FisherF::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum FisherFError { /// `m <= 0` or `nan`. MTooSmall, @@ -462,6 +473,7 @@ where /// println!("{} is from a t(11) distribution", v) /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct StudentT where F: Float, @@ -511,6 +523,7 @@ where /// Communications of the ACM 21, 317-322. /// https://doi.org/10.1145/359460.359482 #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum BetaAlgorithm { BB(BB), BC(BC), @@ -518,6 +531,7 @@ enum BetaAlgorithm { /// Algorithm BB for `min(alpha, beta) > 1`. #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct BB { alpha: N, beta: N, @@ -526,6 +540,7 @@ struct BB { /// Algorithm BC for `min(alpha, beta) <= 1`. #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct BC { alpha: N, beta: N, @@ -546,6 +561,7 @@ struct BC { /// println!("{} is from a Beta(2, 5) distribution", v); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Beta where F: Float, @@ -557,6 +573,7 @@ where /// Error type returned from `Beta::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum BetaError { /// `alpha <= 0` or `nan`. AlphaTooSmall, diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 8cb10576e6a..31bf98c896e 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -26,6 +26,7 @@ use core::fmt; /// println!("{} is from a Geometric(0.25) distribution", v); /// ``` #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Geometric { p: f64, @@ -151,6 +152,7 @@ impl Distribution for Geometric /// println!("{} is from a Geometric(0.5) distribution", v); /// ``` #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct StandardGeometric; impl Distribution for StandardGeometric { @@ -231,4 +233,4 @@ mod test { results.iter().map(|x| (x - mean) * (x - mean)).sum::() / results.len() as f64; assert!((variance - expected_variance).abs() < expected_variance / 10.0); } -} \ No newline at end of file +} diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 507d7bb07f5..801dff4e960 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -6,6 +6,7 @@ use rand::distributions::uniform::Uniform; use core::fmt; #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] enum SamplingMethod { InverseTransform{ initial_p: f64, initial_x: i64 }, RejectionAcceptance{ @@ -43,6 +44,7 @@ enum SamplingMethod { /// println!("{} is from a hypergeometric distribution", v); /// ``` #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Hypergeometric { n1: u64, n2: u64, diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index becb02b64f8..58986a769aa 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -27,6 +27,7 @@ impl std::error::Error for Error {} /// The [inverse Gaussian distribution](https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution) #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct InverseGaussian where F: Float, diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index c3e99010c6f..7078a894f43 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -37,6 +37,7 @@ use core::fmt; /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct StandardNormal; impl Distribution for StandardNormal { @@ -112,6 +113,7 @@ impl Distribution for StandardNormal { /// /// [`StandardNormal`]: crate::StandardNormal #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Normal where F: Float, StandardNormal: Distribution { @@ -226,6 +228,7 @@ where F: Float, StandardNormal: Distribution /// println!("{} is from an ln N(2, 9) distribution", v) /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct LogNormal where F: Float, StandardNormal: Distribution { diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index d8e44587d05..c4d693d031d 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -27,6 +27,7 @@ impl std::error::Error for Error {} /// The [normal-inverse Gaussian distribution](https://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) #[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct NormalInverseGaussian where F: Float, diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index 53e2987fce1..cd61894c526 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -24,6 +24,7 @@ use core::fmt; /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Pareto where F: Float, OpenClosed01: Distribution { diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index e53ea0b89e8..4ead1fb8f74 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -31,6 +31,7 @@ use core::fmt; /// /// [`Triangular`]: crate::Triangular #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Pert where F: Float, diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index dc21ef3e7ce..abc00289445 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -29,6 +29,7 @@ use core::fmt; /// println!("{} is from a Poisson(2) distribution", v); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Poisson where F: Float + FloatConst, Standard: Distribution { diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index 97693b32fcc..ba6d36445ce 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -32,6 +32,7 @@ use core::fmt; /// /// [`Pert`]: crate::Pert #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Triangular where F: Float, Standard: Distribution { diff --git a/rand_distr/src/unit_ball.rs b/rand_distr/src/unit_ball.rs index e5585a1e677..8a4b4fbf3d1 100644 --- a/rand_distr/src/unit_ball.rs +++ b/rand_distr/src/unit_ball.rs @@ -25,6 +25,7 @@ use rand::Rng; /// println!("{:?} is from the unit ball.", v) /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct UnitBall; impl Distribution<[F; 3]> for UnitBall { diff --git a/rand_distr/src/unit_circle.rs b/rand_distr/src/unit_circle.rs index 29e5c9a5939..24a06f3f4de 100644 --- a/rand_distr/src/unit_circle.rs +++ b/rand_distr/src/unit_circle.rs @@ -29,6 +29,7 @@ use rand::Rng; /// NBS Appl. Math. Ser., No. 12. Washington, DC: U.S. Government Printing /// Office, pp. 36-38. #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct UnitCircle; impl Distribution<[F; 2]> for UnitCircle { diff --git a/rand_distr/src/unit_disc.rs b/rand_distr/src/unit_disc.rs index ced548b4dc0..937c1d01b84 100644 --- a/rand_distr/src/unit_disc.rs +++ b/rand_distr/src/unit_disc.rs @@ -24,6 +24,7 @@ use rand::Rng; /// println!("{:?} is from the unit Disc.", v) /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct UnitDisc; impl Distribution<[F; 2]> for UnitDisc { diff --git a/rand_distr/src/unit_sphere.rs b/rand_distr/src/unit_sphere.rs index b167a5d5d63..2b299239f49 100644 --- a/rand_distr/src/unit_sphere.rs +++ b/rand_distr/src/unit_sphere.rs @@ -28,6 +28,7 @@ use rand::Rng; /// Sphere.*](https://doi.org/10.1214/aoms/1177692644) /// Ann. Math. Statist. 43, no. 2, 645--646. #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct UnitSphere; impl Distribution<[F; 3]> for UnitSphere { diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index aa9bdc44405..b390ad3ff2c 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -24,6 +24,7 @@ use core::fmt; /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Weibull where F: Float, OpenClosed01: Distribution { From 9bd8017e208bd04ac016a3fbf1f1102136228e90 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 8 Jul 2021 15:09:53 +0100 Subject: [PATCH 117/443] impl serde for rand_distr::WeightedAliasIndex; fix bounds for Uniform --- rand_distr/Cargo.toml | 2 +- rand_distr/src/weighted_alias.rs | 5 +++++ src/distributions/uniform.rs | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 74611bee567..600157ae33d 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -20,7 +20,7 @@ default = ["std"] std = ["alloc", "rand/std"] alloc = ["rand/alloc"] std_math = ["num-traits/std"] -serde1 = ["serde"] +serde1 = ["serde", "rand/serde1"] [dependencies] rand = { path = "..", version = "0.8.0", default-features = false } diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 53a9c2713d4..2cd90c52a32 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -16,6 +16,8 @@ use core::iter::Sum; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use rand::Rng; use alloc::{boxed::Box, vec, vec::Vec}; +#[cfg(feature = "serde1")] +use serde::{Serialize, Deserialize}; /// A distribution using weighted sampling to pick a discretely selected item. /// @@ -64,6 +66,9 @@ use alloc::{boxed::Box, vec, vec::Vec}; /// [`Uniform::sample`]: Distribution::sample /// [`Uniform::sample`]: Distribution::sample #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde1", serde(bound(serialize = "W: Serialize, W::Sampler: Serialize")))] +#[cfg_attr(feature = "serde1", serde(bound(deserialize = "W: Deserialize<'de>, W::Sampler: Deserialize<'de>")))] pub struct WeightedAliasIndex { aliases: Box<[u32]>, no_alias_odds: Box<[W]>, diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index fb898370329..c28760b0953 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -172,6 +172,8 @@ use serde::{Serialize, Deserialize}; /// [`Rng::gen_range`]: Rng::gen_range #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] +#[cfg_attr(feature = "serde1", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")))] pub struct Uniform(X::Sampler); impl Uniform { From 0e515d7adcd532de384b032d4cd88021a29b73e5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 8 Jul 2021 15:16:53 +0100 Subject: [PATCH 118/443] Update rand_distr readme --- rand_distr/README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/rand_distr/README.md b/rand_distr/README.md index b0118426011..70dec87ca80 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -20,11 +20,7 @@ It is worth mentioning the [statrs] crate which provides similar functionality along with various support functions, including PDF and CDF computation. In contrast, this `rand_distr` crate focuses on sampling from distributions. -If the `std` default feature is enabled, `rand_distr` implements the `Error` -trait for its error types. - -The default `alloc` feature (which is implied by the `std` feature) is required -for some distributions (in particular, `Dirichlet` and `WeightedAliasIndex`). +## Portability and libm The floating point functions from `num_traits` and `libm` are used to support `no_std` environments and ensure reproducibility. If the floating point @@ -32,7 +28,16 @@ functions from `std` are prefered, which may provide better accuracy and performance but may produce different random values, the `std_math` feature can be enabled. -Links: +## Crate features + +- `std` (enabled by default): `rand_distr` implements the `Error` trait for + its error types. Implies `alloc` and `rand/std`. +- `alloc` (enabled by default): required for some distributions when not using + `std` (in particular, `Dirichlet` and `WeightedAliasIndex`). +- `std_math`: see above on portability and libm +- `serde1`: implement (de)seriaialization using `serde` + +## Links - [API documentation (master)](https://rust-random.github.io/rand/rand_distr) - [API documentation (docs.rs)](https://docs.rs/rand_distr) From c60b7cd21be993c5e4f7bb7849b4a789a56ce493 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 8 Jul 2021 16:29:02 +0200 Subject: [PATCH 119/443] Remove special cases for emscripten Because emscripten supports 128-bit integers now, we no longer have to add special cases for it. In particular, we can now use ChaCha12 on all platforms. --- Cargo.toml | 10 ++-------- rand_distr/src/weighted_alias.rs | 6 ------ rand_pcg/src/lib.rs | 3 +-- src/distributions/integer.rs | 9 ++------- src/distributions/uniform.rs | 8 ++------ src/distributions/utils.rs | 4 ---- src/distributions/weighted.rs | 1 - src/rng.rs | 8 ++------ src/rngs/std.rs | 6 +----- 9 files changed, 10 insertions(+), 45 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ce312342cf..0c1b44d8194 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ getrandom = ["rand_core/getrandom"] simd_support = ["packed_simd"] # Option (enabled by default): enable StdRng -std_rng = ["rand_chacha", "rand_hc"] +std_rng = ["rand_chacha"] # Option: enable SmallRng small_rng = [] @@ -58,6 +58,7 @@ members = [ rand_core = { path = "rand_core", version = "0.6.0" } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } +rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = false, optional = true } [dependencies.packed_simd] # NOTE: so far no version works reliably due to dependence on unstable features @@ -70,13 +71,6 @@ features = ["into_bits"] # Used for fork protection (reseeding.rs) libc = { version = "0.2.22", optional = true, default-features = false } -# Emscripten does not support 128-bit integers, which are used by ChaCha code. -# We work around this by using a different RNG. -[target.'cfg(not(target_os = "emscripten"))'.dependencies] -rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = false, optional = true } -[target.'cfg(target_os = "emscripten")'.dependencies] -rand_hc = { path = "rand_hc", version = "0.3.0", optional = true } - [dev-dependencies] rand_pcg = { path = "rand_pcg", version = "0.3.0" } # Only for benches: diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 53a9c2713d4..6692b7b1e00 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -353,16 +353,12 @@ macro_rules! impl_weight_for_int { impl_weight_for_float!(f64); impl_weight_for_float!(f32); impl_weight_for_int!(usize); -#[cfg(not(target_os = "emscripten"))] -#[cfg_attr(doc_cfg, doc(cfg(not(target_os = "emscripten"))))] impl_weight_for_int!(u128); impl_weight_for_int!(u64); impl_weight_for_int!(u32); impl_weight_for_int!(u16); impl_weight_for_int!(u8); impl_weight_for_int!(isize); -#[cfg(not(target_os = "emscripten"))] -#[cfg_attr(doc_cfg, doc(cfg(not(target_os = "emscripten"))))] impl_weight_for_int!(i128); impl_weight_for_int!(i64); impl_weight_for_int!(i32); @@ -401,14 +397,12 @@ mod test { ); } - #[cfg(not(target_os = "emscripten"))] #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_weighted_index_u128() { test_weighted_index(|x: u128| x as f64); } - #[cfg(not(target_os = "emscripten"))] #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_weighted_index_i128() { diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index c25bc439a4c..b14840af2c8 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -37,9 +37,8 @@ #![deny(missing_debug_implementations)] #![no_std] -#[cfg(not(target_os = "emscripten"))] mod pcg128; +mod pcg128; mod pcg64; -#[cfg(not(target_os = "emscripten"))] pub use self::pcg128::{Lcg128Xsl64, Mcg128Xsl64, Pcg64, Pcg64Mcg}; pub use self::pcg64::{Lcg64Xsh32, Pcg32}; diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index 8a2ce4cf1e6..19ce71599cb 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -14,8 +14,8 @@ use crate::Rng; use core::arch::x86::{__m128i, __m256i}; #[cfg(all(target_arch = "x86_64", feature = "simd_support"))] use core::arch::x86_64::{__m128i, __m256i}; -#[cfg(not(target_os = "emscripten"))] use core::num::NonZeroU128; -use core::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}; +use core::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, + NonZeroU128}; #[cfg(feature = "simd_support")] use packed_simd::*; impl Distribution for Standard { @@ -46,7 +46,6 @@ impl Distribution for Standard { } } -#[cfg(not(target_os = "emscripten"))] impl Distribution for Standard { #[inline] fn sample(&self, rng: &mut R) -> u128 { @@ -86,7 +85,6 @@ impl_int_from_uint! { i8, u8 } impl_int_from_uint! { i16, u16 } impl_int_from_uint! { i32, u32 } impl_int_from_uint! { i64, u64 } -#[cfg(not(target_os = "emscripten"))] impl_int_from_uint! { i128, u128 } impl_int_from_uint! { isize, usize } @@ -108,7 +106,6 @@ impl_nzint!(NonZeroU8, NonZeroU8::new); impl_nzint!(NonZeroU16, NonZeroU16::new); impl_nzint!(NonZeroU32, NonZeroU32::new); impl_nzint!(NonZeroU64, NonZeroU64::new); -#[cfg(not(target_os = "emscripten"))] impl_nzint!(NonZeroU128, NonZeroU128::new); impl_nzint!(NonZeroUsize, NonZeroUsize::new); @@ -173,7 +170,6 @@ mod tests { rng.sample::(Standard); rng.sample::(Standard); rng.sample::(Standard); - #[cfg(not(target_os = "emscripten"))] rng.sample::(Standard); rng.sample::(Standard); @@ -181,7 +177,6 @@ mod tests { rng.sample::(Standard); rng.sample::(Standard); rng.sample::(Standard); - #[cfg(not(target_os = "emscripten"))] rng.sample::(Standard); } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 516a58c7072..b604338b0b6 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -556,7 +556,6 @@ uniform_int_impl! { i8, u8, u32 } uniform_int_impl! { i16, u16, u32 } uniform_int_impl! { i32, u32, u32 } uniform_int_impl! { i64, u64, u64 } -#[cfg(not(target_os = "emscripten"))] uniform_int_impl! { i128, u128, u128 } uniform_int_impl! { isize, usize, usize } uniform_int_impl! { u8, u8, u32 } @@ -564,7 +563,6 @@ uniform_int_impl! { u16, u16, u32 } uniform_int_impl! { u32, u32, u32 } uniform_int_impl! { u64, u64, u64 } uniform_int_impl! { usize, usize, usize } -#[cfg(not(target_os = "emscripten"))] uniform_int_impl! { u128, u128, u128 } #[cfg(feature = "simd_support")] @@ -1224,7 +1222,7 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_integers() { - #[cfg(not(target_os = "emscripten"))] use core::{i128, u128}; + use core::{i128, u128}; use core::{i16, i32, i64, i8, isize}; use core::{u16, u32, u64, u8, usize}; @@ -1292,9 +1290,7 @@ mod tests { );)* }}; } - t!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize); - #[cfg(not(target_os = "emscripten"))] - t!(i128, u128); + t!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, i128, u128); #[cfg(feature = "simd_support")] { diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index b11f602004e..109b48d40a1 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -56,7 +56,6 @@ macro_rules! wmul_impl { wmul_impl! { u8, u16, 8 } wmul_impl! { u16, u32, 16 } wmul_impl! { u32, u64, 32 } -#[cfg(not(target_os = "emscripten"))] wmul_impl! { u64, u128, 64 } // This code is a translation of the __mulddi3 function in LLVM's @@ -120,9 +119,6 @@ macro_rules! wmul_impl_large { )+ }; } -#[cfg(target_os = "emscripten")] -wmul_impl_large! { u64, 32 } -#[cfg(not(target_os = "emscripten"))] wmul_impl_large! { u128, 64 } macro_rules! wmul_impl_usize { diff --git a/src/distributions/weighted.rs b/src/distributions/weighted.rs index 6dd9273a506..846b9df9c28 100644 --- a/src/distributions/weighted.rs +++ b/src/distributions/weighted.rs @@ -43,6 +43,5 @@ pub mod alias_method { impl_weight!(f64, f32,); impl_weight!(u8, u16, u32, u64, usize,); impl_weight!(i8, i16, i32, i64, isize,); - #[cfg(not(target_os = "emscripten"))] impl_weight!(u128, i128,); } diff --git a/src/rng.rs b/src/rng.rs index 07643c55958..610dfe28359 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -389,12 +389,8 @@ macro_rules! impl_fill { } } -impl_fill!(u16, u32, u64, usize,); -#[cfg(not(target_os = "emscripten"))] -impl_fill!(u128); -impl_fill!(i8, i16, i32, i64, isize,); -#[cfg(not(target_os = "emscripten"))] -impl_fill!(i128); +impl_fill!(u16, u32, u64, usize, u128,); +impl_fill!(i8, i16, i32, i64, isize, i128,); #[cfg(feature = "min_const_gen")] impl Fill for [T; N] diff --git a/src/rngs/std.rs b/src/rngs/std.rs index 80f84336980..cdae8fab01c 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -10,13 +10,9 @@ use crate::{CryptoRng, Error, RngCore, SeedableRng}; -#[cfg(all(any(test, feature = "std"), not(target_os = "emscripten")))] pub(crate) use rand_chacha::ChaCha12Core as Core; -#[cfg(all(any(test, feature = "std"), target_os = "emscripten"))] -pub(crate) use rand_hc::Hc128Core as Core; -#[cfg(not(target_os = "emscripten"))] use rand_chacha::ChaCha12Rng as Rng; -#[cfg(target_os = "emscripten")] use rand_hc::Hc128Rng as Rng; +use rand_chacha::ChaCha12Rng as Rng; /// The standard RNG. The PRNG algorithm in `StdRng` is chosen to be efficient /// on the current platform, to be statistically strong and unpredictable From 564a504d59f7396e6a9ea72cce808f6a1fd21f79 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 9 Jul 2021 09:33:10 +0100 Subject: [PATCH 120/443] Update changelog --- rand_distr/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 346be3c26bb..9b7ac93628d 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add mean and std_dev accessors to Normal (#1114) - Make sure all distributions and their error types implement `Error`, `Display`, `Clone`, `Copy`, `PartialEq` and `Eq` as appropriate (#1126) +- Support serde for distributions (#1141) ## [0.4.0] - 2020-12-18 - Bump `rand` to v0.8.0 From fb6390f9d35e715f274d986d3be971b8d2749b00 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Tue, 13 Jul 2021 23:18:57 +1000 Subject: [PATCH 121/443] Fix build on non-32/64-bit architectures (#1144) * Impl WideningMultiply for 16-bit pointers * Add AVR CI test job --- .github/workflows/test.yml | 14 ++++++++++++++ src/distributions/utils.rs | 2 ++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8355d6b7c23..6c48d1e731e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -174,6 +174,20 @@ jobs: - name: Build top-level only run: cargo build --target=thumbv6m-none-eabi --no-default-features + test-avr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2021-01-07 # Pinned compiler version due to https://github.com/rust-lang/compiler-builtins/issues/400 + components: rust-src + override: true + - name: Build top-level only + run: cargo build -Z build-std=core --target=avr-unknown-gnu-atmega328 --no-default-features + test-ios: runs-on: macos-latest steps: diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index b11f602004e..0d50c91fa1f 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -138,6 +138,8 @@ macro_rules! wmul_impl_usize { } }; } +#[cfg(target_pointer_width = "16")] +wmul_impl_usize! { u16 } #[cfg(target_pointer_width = "32")] wmul_impl_usize! { u32 } #[cfg(target_pointer_width = "64")] From 1145ba0200b771da03b69493844bd2841c51cf44 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 13 Jul 2021 15:23:39 +0200 Subject: [PATCH 122/443] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b37ca35a05f..6e87add21bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [0.8.5] - unreleased +### Fixes +- Fix build on non-32/64-bit architectures (#1144) + ## [0.8.4] - 2021-06-15 ### Additions - Use const-generics to support arrays of all sizes (#1104) From 82b819ab678ce38ebee2878961ffa78608f19b32 Mon Sep 17 00:00:00 2001 From: rodrimati1992 Date: Sun, 25 Jul 2021 18:42:43 -0300 Subject: [PATCH 123/443] Documented needed crate feature --- src/rng.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rng.rs b/src/rng.rs index 610dfe28359..79a9fbff46e 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -392,6 +392,7 @@ macro_rules! impl_fill { impl_fill!(u16, u32, u64, usize, u128,); impl_fill!(i8, i16, i32, i64, isize, i128,); +#[cfg_attr(doc_cfg, doc(cfg(feature = "min_const_gen")))] #[cfg(feature = "min_const_gen")] impl Fill for [T; N] where [T]: Fill From a36047f0dc3225ff5e7cdeec249d87c36d605e33 Mon Sep 17 00:00:00 2001 From: rodrimati1992 Date: Sun, 25 Jul 2021 18:44:25 -0300 Subject: [PATCH 124/443] Documented needed crate feature --- src/distributions/other.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 71fb267f8b5..0935d055b3e 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -186,6 +186,7 @@ tuple_impl! {A, B, C, D, E, F, G, H, I, J} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K, L} +#[cfg_attr(doc_cfg, doc(cfg(feature = "min_const_gen")))] #[cfg(feature = "min_const_gen")] impl Distribution<[T; N]> for Standard where Standard: Distribution From d4256fcc6df77eb8da68ada74be5c30f040fe9ad Mon Sep 17 00:00:00 2001 From: rodrimati1992 Date: Sun, 25 Jul 2021 18:53:19 -0300 Subject: [PATCH 125/443] Added min_const_gen feature to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2f313e39ca9..c6834824a1e 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,8 @@ Additionally, these features configure Rand: - `nightly` enables some optimizations requiring nightly Rust - `simd_support` (experimental) enables sampling of SIMD values (uniformly random SIMD integers and floats), requiring nightly Rust +- `min_const_gen` enables generating random arrays of + any size using min-const-generics, requiring Rust ≥ 1.51. Note that nightly features are not stable and therefore not all library and compiler versions will be compatible. This is especially true of Rand's From a49e4ad730c05c332163eade42f7c8ed51498f92 Mon Sep 17 00:00:00 2001 From: fossdd Date: Mon, 26 Jul 2021 09:17:42 +0000 Subject: [PATCH 126/443] Remove notice by Apache License Authors The notice that we can add a license notice before every file can be ignored, as rand already has a own copyright notice. --- LICENSE-APACHE | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 17d74680f8c..494ad3bfdfe 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. From 894cf45fad55b805d781871981975a0d36e3a939 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 27 Jul 2021 15:52:23 -0400 Subject: [PATCH 127/443] Propagate std and getrandom features --- rand_chacha/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 53f9f591107..17f82fcb401 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -25,6 +25,7 @@ serde_json = "1.0" [features] default = ["std"] -std = ["ppv-lite86/std"] +std = ["ppv-lite86/std", "rand_core/std"] +getrandom = ["rand_core/getrandom"] simd = [] # deprecated serde1 = ["serde"] From 85f55b278afef3c181237e2460626224945df0b9 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 27 Jul 2021 21:33:07 +0200 Subject: [PATCH 128/443] Zeta and Zipf: Improve docs --- rand_distr/src/zipf.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 41d10f4ec0b..2ea2177c5a4 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -13,9 +13,14 @@ use crate::{Distribution, Standard}; use rand::{Rng, distributions::OpenClosed01}; use core::fmt; -/// Samples floating-point numbers according to the zeta distribution. +/// Samples integers according to the [zeta distribution]. /// -/// The zeta distribution is a limit of the [`Zipf`] distribution. +/// The zeta distribution is a limit of the [`Zipf`] distribution. Sometimes it +/// is called one of the following: discrete Pareto, Riemann-Zeta, Zipf, or +/// Zipf–Estoup distribution. +/// +/// It has the density function `f(k) = k^(-a) / C(a)` for `k >= 1`, where `a` +/// is the parameter and `C(a)` is the Riemann zeta function. /// /// # Example /// ``` @@ -25,6 +30,8 @@ use core::fmt; /// let val: f64 = thread_rng().sample(Zeta::new(1.5).unwrap()); /// println!("{}", val); /// ``` +/// +/// [zeta distribution]: https://en.wikipedia.org/wiki/Zeta_distribution #[derive(Clone, Copy, Debug)] pub struct Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution @@ -75,7 +82,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution { #[inline] fn sample(&self, rng: &mut R) -> F { - // This is based on the numpy implementation. + // This is based on https://doi.org/10.1007/978-1-4613-8643-8. loop { let u = rng.sample(OpenClosed01); let v = rng.sample(Standard); @@ -93,10 +100,11 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution } } -/// Samples floating-point numbers according to the Zipf distribution. +/// Samples integers according to the Zipf distribution. /// -/// The samples follow Zipf's law: The frequency of each sample is inversely -/// proportional to a power of its frequency rank. +/// The samples follow Zipf's law: The frequency of each sample from a finite +/// set of size `n` is inversely proportional to a power of its frequency rank +/// (with exponent `s`). /// /// For large `n`, this converges to the [`Zeta`] distribution. /// From 2a33433247751fbb6803c879821745bea2175430 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 27 Jul 2021 21:34:52 +0200 Subject: [PATCH 129/443] Zeta: Replace likely impossible if with debug_assert --- rand_distr/src/zipf.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 2ea2177c5a4..7f20634c46a 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -85,14 +85,12 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution // This is based on https://doi.org/10.1007/978-1-4613-8643-8. loop { let u = rng.sample(OpenClosed01); - let v = rng.sample(Standard); let x = u.powf(-F::one() / self.a_minus_1).floor(); - - if x < F::one() { - continue; - } + debug_assert!(x >= F::one()); let t = (F::one() + F::one() / x).powf(self.a_minus_1); + + let v = rng.sample(Standard); if v * x * (t - F::one()) / (self.b - F::one()) <= t / self.b { return x; } From c8f3bac0f7a650c4db21ec9b64956234d78cd4cf Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 27 Jul 2021 16:16:45 -0400 Subject: [PATCH 130/443] Updated changelog --- rand_chacha/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index a598bb7ee6c..d953cee0947 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +- Made `rand_chacha` propagate the `std` and `getrandom` features down to `rand_core` + ## [0.3.1] - 2021-06-09 - add getters corresponding to existing setters: `get_seed`, `get_stream` (#1124) - add serde support, gated by the `serde1` feature (#1124) From 6b0f550d41688c29000337268ebfcdacdfcf7b02 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 27 Jul 2021 16:32:08 -0400 Subject: [PATCH 131/443] Removed getrandom feature --- rand_chacha/CHANGELOG.md | 2 +- rand_chacha/Cargo.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index d953cee0947..1367b34a3d7 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -- Made `rand_chacha` propagate the `std` and `getrandom` features down to `rand_core` +- Made `rand_chacha` propagate the `std` feature down to `rand_core` ## [0.3.1] - 2021-06-09 - add getters corresponding to existing setters: `get_seed`, `get_stream` (#1124) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 17f82fcb401..f99d1967133 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -26,6 +26,5 @@ serde_json = "1.0" [features] default = ["std"] std = ["ppv-lite86/std", "rand_core/std"] -getrandom = ["rand_core/getrandom"] simd = [] # deprecated serde1 = ["serde"] From 475de9a0f6d930274aaac5ced1fe0bcac74e6b7e Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 27 Jul 2021 16:35:07 -0400 Subject: [PATCH 132/443] Updated readme --- rand_chacha/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rand_chacha/README.md b/rand_chacha/README.md index edd754d791e..ec500fd8528 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -36,7 +36,8 @@ Links: `rand_chacha` is `no_std` compatible when disabling default features; the `std` feature can be explicitly required to re-enable `std` support. Using `std` -allows detection of CPU features and thus better optimisation. +allows detection of CPU features and thus better optimisation. Using `std` +also enables `getrandom` functionality, such as `ChaCha20::from_entropy()`. # License From cc138071494959f2d9a3b97ba71ce884f16b555c Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 27 Jul 2021 16:36:06 -0400 Subject: [PATCH 133/443] Typo --- rand_chacha/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_chacha/README.md b/rand_chacha/README.md index ec500fd8528..1a6920d94f8 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -37,7 +37,7 @@ Links: `rand_chacha` is `no_std` compatible when disabling default features; the `std` feature can be explicitly required to re-enable `std` support. Using `std` allows detection of CPU features and thus better optimisation. Using `std` -also enables `getrandom` functionality, such as `ChaCha20::from_entropy()`. +also enables `getrandom` functionality, such as `ChaCha20Rng::from_entropy()`. # License From e19349c0cf3d585c8adbb8ef1fac8943d6242399 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 28 Jul 2021 17:39:22 +0200 Subject: [PATCH 134/443] Give credit for implementation details --- rand_distr/src/zipf.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 7f20634c46a..9d981fdc1e1 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -116,6 +116,13 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution /// let val: f64 = thread_rng().sample(Zipf::new(10, 1.5).unwrap()); /// println!("{}", val); /// ``` +/// +/// # Implementation details +/// +/// Implemented via [rejection sampling](https://en.wikipedia.org/wiki/Rejection_sampling), +/// due to Jason Crease[1]. +/// +/// [1]: https://jasoncrease.medium.com/rejection-sampling-the-zipf-distribution-6b359792cffa #[derive(Clone, Copy, Debug)] pub struct Zipf where F: Float, Standard: Distribution { From a746fd2369b1085593f0482ae0baf3ba7d185186 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 28 Jul 2021 18:23:03 +0200 Subject: [PATCH 135/443] Zipf: Fix `inv_cdf` for `s = 1` --- rand_distr/src/zipf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 9d981fdc1e1..dfc4e27c84a 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -188,7 +188,7 @@ where F: Float, Standard: Distribution { } else if self.s != F::one() { (pt * (one - self.s) + self.s).powf(one / (one - self.s)) } else { - pt.exp() + (pt - one).exp() } } } From b053683ee44595fc7850603c7144b56f609ff2bd Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 28 Jul 2021 18:26:45 +0200 Subject: [PATCH 136/443] Zipf: Correctly calculate rejection ratio --- rand_distr/src/zipf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index dfc4e27c84a..a3aeaea2f65 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -202,7 +202,7 @@ where F: Float, Standard: Distribution loop { let inv_b = self.inv_cdf(rng.sample(Standard)); let x = (inv_b + one).floor(); - let mut ratio = x.powf(-self.s) * self.t; + let mut ratio = x.powf(-self.s); if x > one { ratio = ratio * inv_b.powf(self.s) }; From 0f9243c4a642f43d4e9a66b687132f507ff08823 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 28 Jul 2021 19:12:58 +0200 Subject: [PATCH 137/443] Zipf: Add debug_assert for invariant --- rand_distr/src/zipf.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index a3aeaea2f65..14f146c165a 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -173,6 +173,7 @@ where F: Float, Standard: Distribution { } else { F::one() + n.ln() }; + debug_assert!(t > F::zero()); Ok(Zipf { n, s, t }) From e5aff9a10df6580b82a34219e8fb0f5ba78772f1 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 30 Jul 2021 13:15:58 +0200 Subject: [PATCH 138/443] Zipf: Avoid division inside loop --- rand_distr/src/zipf.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 14f146c165a..f6e71b53542 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -129,6 +129,7 @@ where F: Float, Standard: Distribution { n: F, s: F, t: F, + q: F, } /// Error type returned from `Zipf::new`. @@ -168,14 +169,21 @@ where F: Float, Standard: Distribution { return Err(ZipfError::NTooSmall); } let n = F::from(n).unwrap(); // This does not fail. + let q = if s != F::one() { + // Make sure to calculate the division only once. + F::one() / (F::one() - s) + } else { + // This value is never used. + F::zero() + }; let t = if s != F::one() { - (n.powf(F::one() - s) - s) / (F::one() - s) + (n.powf(F::one() - s) - s) * q } else { F::one() + n.ln() }; debug_assert!(t > F::zero()); Ok(Zipf { - n, s, t + n, s, t, q }) } @@ -186,8 +194,8 @@ where F: Float, Standard: Distribution { let pt = p * self.t; if pt <= one { pt - } else if self.s != F::one() { - (pt * (one - self.s) + self.s).powf(one / (one - self.s)) + } else if self.s != one { + (pt * (one - self.s) + self.s).powf(self.q) } else { (pt - one).exp() } From a32cd08eb4feea86e10f963ef0ae7ba2c248112c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 30 Jul 2021 13:21:21 +0200 Subject: [PATCH 139/443] Zeta: Mention algorithm in doc comment --- rand_distr/src/zipf.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index f6e71b53542..5573c628426 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -31,7 +31,13 @@ use core::fmt; /// println!("{}", val); /// ``` /// +/// # Implementation details +/// +/// We are using the algorithm from [Non-Uniform Random Variate Generation], +/// Section 6.1, page 551. +/// /// [zeta distribution]: https://en.wikipedia.org/wiki/Zeta_distribution +/// [Non-Uniform Random Variate Generation]: https://doi.org/10.1007/978-1-4613-8643-8 #[derive(Clone, Copy, Debug)] pub struct Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution @@ -82,7 +88,6 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution { #[inline] fn sample(&self, rng: &mut R) -> F { - // This is based on https://doi.org/10.1007/978-1-4613-8643-8. loop { let u = rng.sample(OpenClosed01); let x = u.powf(-F::one() / self.a_minus_1).floor(); From 72a63339b78ab270d02a08a191121c6d8b423937 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 30 Jul 2021 14:29:04 +0200 Subject: [PATCH 140/443] Zeta: Avoid division in rejection criterion --- rand_distr/src/zipf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 5573c628426..84636b881a9 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -96,7 +96,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution let t = (F::one() + F::one() / x).powf(self.a_minus_1); let v = rng.sample(Standard); - if v * x * (t - F::one()) / (self.b - F::one()) <= t / self.b { + if v * x * (t - F::one()) * self.b <= t * (self.b - F::one()) { return x; } } From cf4b7e46713b834a61b3c279de09c5a9de697b7a Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 2 Aug 2021 23:34:14 +0200 Subject: [PATCH 141/443] Zeta: Fix infinite loop for small `a` --- rand_distr/src/zipf.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 84636b881a9..8b012eaa6e1 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -92,6 +92,12 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution let u = rng.sample(OpenClosed01); let x = u.powf(-F::one() / self.a_minus_1).floor(); debug_assert!(x >= F::one()); + if x.is_infinite() { + // For sufficiently small `a`, `x` will always be infinite, + // which is rejected, resulting in an infinite loop. We avoid + // this by always returning infinity instead. + return x; + } let t = (F::one() + F::one() / x).powf(self.a_minus_1); @@ -267,6 +273,17 @@ mod tests { } } + #[test] + fn zeta_small_a() { + let a = 1. + 1e-15; + let d = Zeta::new(a).unwrap(); + let mut rng = crate::test::rng(2); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 1.); + } + } + #[test] fn zeta_value_stability() { test_samples(Zeta::new(1.5).unwrap(), 0f32, &[ From fe5a6e10d473056ad1f07b5240db15fab1bcc977 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 3 Aug 2021 23:56:35 +0200 Subject: [PATCH 142/443] Zeta: Document cases where infinity is returned --- rand_distr/src/zipf.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 8b012eaa6e1..ad322c4e1b3 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -31,6 +31,14 @@ use core::fmt; /// println!("{}", val); /// ``` /// +/// # Remarks +/// +/// The zeta distribution has no upper limit. Sampled values may be infinite. +/// In particular, a value of infinity might be returned for the following +/// reasons: +/// 1. it is the best representation in the type `F` of the actual sample. +/// 2. to prevent infinite loops for very small `a`. +/// /// # Implementation details /// /// We are using the algorithm from [Non-Uniform Random Variate Generation], From 067238f05753d18c7cc032717bef03a7f5244a8c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 5 Aug 2021 14:59:07 +0200 Subject: [PATCH 143/443] Remove `rand_hc` (#1156) * Remove `rand_hc` We no longer need it for `rand`, so it can be moved to the `rngs` repository. --- .github/workflows/test.yml | 4 - Cargo.toml | 3 - SECURITY.md | 1 - benches/generators.rs | 5 - rand_hc/CHANGELOG.md | 25 -- rand_hc/COPYRIGHT | 12 - rand_hc/Cargo.toml | 18 -- rand_hc/LICENSE-APACHE | 201 -------------- rand_hc/LICENSE-MIT | 25 -- rand_hc/README.md | 44 ---- rand_hc/src/hc128.rs | 519 ------------------------------------- rand_hc/src/lib.rs | 23 -- 12 files changed, 880 deletions(-) delete mode 100644 rand_hc/CHANGELOG.md delete mode 100644 rand_hc/COPYRIGHT delete mode 100644 rand_hc/Cargo.toml delete mode 100644 rand_hc/LICENSE-APACHE delete mode 100644 rand_hc/LICENSE-MIT delete mode 100644 rand_hc/README.md delete mode 100644 rand_hc/src/hc128.rs delete mode 100644 rand_hc/src/lib.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c48d1e731e..eaee94097d7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,8 +99,6 @@ jobs: run: cargo test --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde1 - name: Test rand_chacha run: cargo test --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml - - name: Test rand_hc - run: cargo test --target ${{ matrix.target }} --manifest-path rand_hc/Cargo.toml test-cross: runs-on: ${{ matrix.os }} @@ -137,7 +135,6 @@ jobs: cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde1 cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde1 cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml - cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_hc/Cargo.toml test-miri: runs-on: ubuntu-latest @@ -158,7 +155,6 @@ jobs: #cargo miri test --manifest-path rand_distr/Cargo.toml # no unsafe and lots of slow tests cargo miri test --manifest-path rand_pcg/Cargo.toml --features=serde1 cargo miri test --manifest-path rand_chacha/Cargo.toml --no-default-features - cargo miri test --manifest-path rand_hc/Cargo.toml test-no-std: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 0c1b44d8194..1597b1257d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,6 @@ members = [ "rand_core", "rand_distr", "rand_chacha", - "rand_hc", "rand_pcg", ] @@ -73,8 +72,6 @@ libc = { version = "0.2.22", optional = true, default-features = false } [dev-dependencies] rand_pcg = { path = "rand_pcg", version = "0.3.0" } -# Only for benches: -rand_hc = { path = "rand_hc", version = "0.3.0" } # Only to test serde1 bincode = "1.2.1" diff --git a/SECURITY.md b/SECURITY.md index bad366c1fd8..a31b4e23fd3 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -49,7 +49,6 @@ exceptions for theoretical issues without a known exploit: | `rand` | 0.4 | Jitter, ISAAC | | `rand_core` | 0.2 - 0.6 | | | `rand_chacha` | 0.1 - 0.3 | | -| `rand_hc` | 0.1 - 0.3 | | Explanation of exceptions: diff --git a/benches/generators.rs b/benches/generators.rs index 3e264083d7d..f59c22224fb 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -21,7 +21,6 @@ use rand::prelude::*; use rand::rngs::adapter::ReseedingRng; use rand::rngs::{mock::StepRng, OsRng}; use rand_chacha::{ChaCha12Rng, ChaCha20Core, ChaCha20Rng, ChaCha8Rng}; -use rand_hc::Hc128Rng; use rand_pcg::{Pcg32, Pcg64, Pcg64Mcg}; macro_rules! gen_bytes { @@ -48,7 +47,6 @@ gen_bytes!(gen_bytes_pcg64mcg, Pcg64Mcg::from_entropy()); gen_bytes!(gen_bytes_chacha8, ChaCha8Rng::from_entropy()); gen_bytes!(gen_bytes_chacha12, ChaCha12Rng::from_entropy()); gen_bytes!(gen_bytes_chacha20, ChaCha20Rng::from_entropy()); -gen_bytes!(gen_bytes_hc128, Hc128Rng::from_entropy()); gen_bytes!(gen_bytes_std, StdRng::from_entropy()); #[cfg(feature = "small_rng")] gen_bytes!(gen_bytes_small, SmallRng::from_entropy()); @@ -78,7 +76,6 @@ gen_uint!(gen_u32_pcg64mcg, u32, Pcg64Mcg::from_entropy()); gen_uint!(gen_u32_chacha8, u32, ChaCha8Rng::from_entropy()); gen_uint!(gen_u32_chacha12, u32, ChaCha12Rng::from_entropy()); gen_uint!(gen_u32_chacha20, u32, ChaCha20Rng::from_entropy()); -gen_uint!(gen_u32_hc128, u32, Hc128Rng::from_entropy()); gen_uint!(gen_u32_std, u32, StdRng::from_entropy()); #[cfg(feature = "small_rng")] gen_uint!(gen_u32_small, u32, SmallRng::from_entropy()); @@ -91,7 +88,6 @@ gen_uint!(gen_u64_pcg64mcg, u64, Pcg64Mcg::from_entropy()); gen_uint!(gen_u64_chacha8, u64, ChaCha8Rng::from_entropy()); gen_uint!(gen_u64_chacha12, u64, ChaCha12Rng::from_entropy()); gen_uint!(gen_u64_chacha20, u64, ChaCha20Rng::from_entropy()); -gen_uint!(gen_u64_hc128, u64, Hc128Rng::from_entropy()); gen_uint!(gen_u64_std, u64, StdRng::from_entropy()); #[cfg(feature = "small_rng")] gen_uint!(gen_u64_small, u64, SmallRng::from_entropy()); @@ -113,7 +109,6 @@ macro_rules! init_gen { init_gen!(init_pcg32, Pcg32); init_gen!(init_pcg64, Pcg64); init_gen!(init_pcg64mcg, Pcg64Mcg); -init_gen!(init_hc128, Hc128Rng); init_gen!(init_chacha, ChaCha20Rng); const RESEEDING_BYTES_LEN: usize = 1024 * 1024; diff --git a/rand_hc/CHANGELOG.md b/rand_hc/CHANGELOG.md deleted file mode 100644 index 868679945e0..00000000000 --- a/rand_hc/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.3.1] - 2021-06-15 -- Adjust crate links - -## [0.3.0] - 2020-12-08 -- Bump `rand_core` version to 0.6.0 -- Bump MSRV to 1.36 (#1011) -- impl PartialEq+Eq for Hc128Rng and Hc128Core (#979) -- Drop some unsafe code, fixing an unsound internal function (#960) - -## [0.2.0] - 2019-06-12 -- Bump minor crate version since rand_core bump is a breaking change -- Switch to Edition 2018 - -## [0.1.1] - 2019-06-06 - yanked -- Bump `rand_core` version -- Adjust usage of `#[inline]` - -## [0.1.0] - 2018-10-17 -- Pulled out of the Rand crate diff --git a/rand_hc/COPYRIGHT b/rand_hc/COPYRIGHT deleted file mode 100644 index 468d907caf9..00000000000 --- a/rand_hc/COPYRIGHT +++ /dev/null @@ -1,12 +0,0 @@ -Copyrights in the Rand project are retained by their contributors. No -copyright assignment is required to contribute to the Rand project. - -For full authorship information, see the version control history. - -Except as otherwise noted (below and/or in individual files), Rand is -licensed under the Apache License, Version 2.0 or - or the MIT license - or , at your option. - -The Rand project includes code from the Rust project -published under these same licenses. diff --git a/rand_hc/Cargo.toml b/rand_hc/Cargo.toml deleted file mode 100644 index 28da55aaeca..00000000000 --- a/rand_hc/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "rand_hc" -version = "0.3.1" -authors = ["The Rand Project Developers"] -license = "MIT OR Apache-2.0" -readme = "README.md" -repository = "https://github.com/rust-random/rand" -documentation = "https://docs.rs/rand_hc" -homepage = "https://rust-random.github.io/book" -description = """ -HC128 random number generator -""" -keywords = ["random", "rng", "hc128"] -categories = ["algorithms", "no-std"] -edition = "2018" - -[dependencies] -rand_core = { path = "../rand_core", version = "0.6.0" } diff --git a/rand_hc/LICENSE-APACHE b/rand_hc/LICENSE-APACHE deleted file mode 100644 index 17d74680f8c..00000000000 --- a/rand_hc/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/rand_hc/LICENSE-MIT b/rand_hc/LICENSE-MIT deleted file mode 100644 index cf656074cbf..00000000000 --- a/rand_hc/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2018 Developers of the Rand project - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/rand_hc/README.md b/rand_hc/README.md deleted file mode 100644 index 87f1c8915c1..00000000000 --- a/rand_hc/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# rand_hc - -[![Test Status](https://github.com/rust-random/rand/workflows/Tests/badge.svg?event=push)](https://github.com/rust-random/rand/actions) -[![Latest version](https://img.shields.io/crates/v/rand_hc.svg)](https://crates.io/crates/rand_hc) -[[![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) -[![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_hc) -[![API](https://docs.rs/rand_hc/badge.svg)](https://docs.rs/rand_hc) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) - -A cryptographically secure random number generator that uses the HC-128 -algorithm. - -HC-128 is a stream cipher designed by Hongjun Wu[^1], that we use as an -RNG. It is selected as one of the "stream ciphers suitable for widespread -adoption" by eSTREAM[^2]. - -Links: - -- [API documentation (master)](https://rust-random.github.io/rand/rand_hc) -- [API documentation (docs.rs)](https://docs.rs/rand_hc) -- [Changelog](https://github.com/rust-random/rand/blob/master/rand_hc/CHANGELOG.md) - -[rand]: https://crates.io/crates/rand -[^1]: Hongjun Wu (2008). ["The Stream Cipher HC-128"]( - http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf). - *The eSTREAM Finalists*, LNCS 4986, pp. 39–47, Springer-Verlag. - -[^2]: [eSTREAM: the ECRYPT Stream Cipher Project]( - http://www.ecrypt.eu.org/stream/) - - -## Crate Features - -`rand_hc` is `no_std` compatible. It does not require any functionality -outside of the `core` lib, thus there are no features to configure. - - -# License - -`rand_hc` is distributed under the terms of both the MIT license and the -Apache License (Version 2.0). - -See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT), and -[COPYRIGHT](COPYRIGHT) for details. diff --git a/rand_hc/src/hc128.rs b/rand_hc/src/hc128.rs deleted file mode 100644 index c2af7506a08..00000000000 --- a/rand_hc/src/hc128.rs +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// Disable some noisy clippy lints. -#![allow(clippy::many_single_char_names)] -#![allow(clippy::identity_op)] -// Disable a lint that cannot be fixed without increasing the MSRV -#![allow(clippy::op_ref)] - -//! The HC-128 random number generator. - -use core::fmt; -use rand_core::block::{BlockRng, BlockRngCore}; -use rand_core::{le, CryptoRng, Error, RngCore, SeedableRng}; - -const SEED_WORDS: usize = 8; // 128 bit key followed by 128 bit iv - -/// A cryptographically secure random number generator that uses the HC-128 -/// algorithm. -/// -/// HC-128 is a stream cipher designed by Hongjun Wu[^1], that we use as an -/// RNG. It is selected as one of the "stream ciphers suitable for widespread -/// adoption" by eSTREAM[^2]. -/// -/// HC-128 is an array based RNG. In this it is similar to RC-4 and ISAAC before -/// it, but those have never been proven cryptographically secure (or have even -/// been significantly compromised, as in the case of RC-4[^5]). -/// -/// Because HC-128 works with simple indexing into a large array and with a few -/// operations that parallelize well, it has very good performance. The size of -/// the array it needs, 4kb, can however be a disadvantage. -/// -/// This implementation is not based on the version of HC-128 submitted to the -/// eSTREAM contest, but on a later version by the author with a few small -/// improvements from December 15, 2009[^3]. -/// -/// HC-128 has no known weaknesses that are easier to exploit than doing a -/// brute-force search of 2128. A very comprehensive analysis of the -/// current state of known attacks / weaknesses of HC-128 is given in *Some -/// Results On Analysis And Implementation Of HC-128 Stream Cipher*[^4]. -/// -/// The average cycle length is expected to be -/// 21024*32+10-1 = 232777. -/// We support seeding with a 256-bit array, which matches the 128-bit key -/// concatenated with a 128-bit IV from the stream cipher. -/// -/// This implementation uses an output buffer of sixteen `u32` words, and uses -/// [`BlockRng`] to implement the [`RngCore`] methods. -/// -/// ## References -/// [^1]: Hongjun Wu (2008). ["The Stream Cipher HC-128"]( -/// http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf). -/// *The eSTREAM Finalists*, LNCS 4986, pp. 39–47, Springer-Verlag. -/// -/// [^2]: [eSTREAM: the ECRYPT Stream Cipher Project]( -/// http://www.ecrypt.eu.org/stream/) -/// -/// [^3]: Hongjun Wu, [Stream Ciphers HC-128 and HC-256]( -/// https://www.ntu.edu.sg/home/wuhj/research/hc/index.html) -/// -/// [^4]: Shashwat Raizada (January 2015),["Some Results On Analysis And -/// Implementation Of HC-128 Stream Cipher"]( -/// http://library.isical.ac.in:8080/jspui/bitstream/123456789/6636/1/TH431.pdf). -/// -/// [^5]: Internet Engineering Task Force (February 2015), -/// ["Prohibiting RC4 Cipher Suites"](https://tools.ietf.org/html/rfc7465). -#[derive(Clone, Debug)] -pub struct Hc128Rng(BlockRng); - -impl RngCore for Hc128Rng { - #[inline] - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - - #[inline] - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } - - #[inline] - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest) - } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.0.try_fill_bytes(dest) - } -} - -impl SeedableRng for Hc128Rng { - type Seed = ::Seed; - - #[inline] - fn from_seed(seed: Self::Seed) -> Self { - Hc128Rng(BlockRng::::from_seed(seed)) - } - - #[inline] - fn from_rng(rng: R) -> Result { - BlockRng::::from_rng(rng).map(Hc128Rng) - } -} - -impl CryptoRng for Hc128Rng {} - -impl PartialEq for Hc128Rng { - fn eq(&self, rhs: &Self) -> bool { - self.0.core == rhs.0.core && self.0.index() == rhs.0.index() - } -} -impl Eq for Hc128Rng {} - -/// The core of `Hc128Rng`, used with `BlockRng`. -#[derive(Clone)] -pub struct Hc128Core { - t: [u32; 1024], - counter1024: usize, -} - -// Custom Debug implementation that does not expose the internal state -impl fmt::Debug for Hc128Core { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Hc128Core {{}}") - } -} - -impl BlockRngCore for Hc128Core { - type Item = u32; - type Results = [u32; 16]; - - fn generate(&mut self, results: &mut Self::Results) { - assert!(self.counter1024 % 16 == 0); - - let cc = self.counter1024 % 512; - let dd = (cc + 16) % 512; - let ee = cc.wrapping_sub(16) % 512; - // These asserts let the compiler optimize out the bounds checks. - // Some of them may be superfluous, and that's fine: - // they'll be optimized out if that's the case. - assert!(ee + 15 < 512); - assert!(cc + 15 < 512); - assert!(dd < 512); - - if self.counter1024 & 512 == 0 { - // P block - results[0] = self.step_p(cc+0, cc+1, ee+13, ee+6, ee+4); - results[1] = self.step_p(cc+1, cc+2, ee+14, ee+7, ee+5); - results[2] = self.step_p(cc+2, cc+3, ee+15, ee+8, ee+6); - results[3] = self.step_p(cc+3, cc+4, cc+0, ee+9, ee+7); - results[4] = self.step_p(cc+4, cc+5, cc+1, ee+10, ee+8); - results[5] = self.step_p(cc+5, cc+6, cc+2, ee+11, ee+9); - results[6] = self.step_p(cc+6, cc+7, cc+3, ee+12, ee+10); - results[7] = self.step_p(cc+7, cc+8, cc+4, ee+13, ee+11); - results[8] = self.step_p(cc+8, cc+9, cc+5, ee+14, ee+12); - results[9] = self.step_p(cc+9, cc+10, cc+6, ee+15, ee+13); - results[10] = self.step_p(cc+10, cc+11, cc+7, cc+0, ee+14); - results[11] = self.step_p(cc+11, cc+12, cc+8, cc+1, ee+15); - results[12] = self.step_p(cc+12, cc+13, cc+9, cc+2, cc+0); - results[13] = self.step_p(cc+13, cc+14, cc+10, cc+3, cc+1); - results[14] = self.step_p(cc+14, cc+15, cc+11, cc+4, cc+2); - results[15] = self.step_p(cc+15, dd+0, cc+12, cc+5, cc+3); - } else { - // Q block - results[0] = self.step_q(cc+0, cc+1, ee+13, ee+6, ee+4); - results[1] = self.step_q(cc+1, cc+2, ee+14, ee+7, ee+5); - results[2] = self.step_q(cc+2, cc+3, ee+15, ee+8, ee+6); - results[3] = self.step_q(cc+3, cc+4, cc+0, ee+9, ee+7); - results[4] = self.step_q(cc+4, cc+5, cc+1, ee+10, ee+8); - results[5] = self.step_q(cc+5, cc+6, cc+2, ee+11, ee+9); - results[6] = self.step_q(cc+6, cc+7, cc+3, ee+12, ee+10); - results[7] = self.step_q(cc+7, cc+8, cc+4, ee+13, ee+11); - results[8] = self.step_q(cc+8, cc+9, cc+5, ee+14, ee+12); - results[9] = self.step_q(cc+9, cc+10, cc+6, ee+15, ee+13); - results[10] = self.step_q(cc+10, cc+11, cc+7, cc+0, ee+14); - results[11] = self.step_q(cc+11, cc+12, cc+8, cc+1, ee+15); - results[12] = self.step_q(cc+12, cc+13, cc+9, cc+2, cc+0); - results[13] = self.step_q(cc+13, cc+14, cc+10, cc+3, cc+1); - results[14] = self.step_q(cc+14, cc+15, cc+11, cc+4, cc+2); - results[15] = self.step_q(cc+15, dd+0, cc+12, cc+5, cc+3); - } - self.counter1024 = self.counter1024.wrapping_add(16); - } -} - -impl Hc128Core { - // One step of HC-128, update P and generate 32 bits keystream - #[inline(always)] - fn step_p(&mut self, i: usize, i511: usize, i3: usize, i10: usize, i12: usize) -> u32 { - let (p, q) = self.t.split_at_mut(512); - let temp0 = p[i511].rotate_right(23); - let temp1 = p[i3].rotate_right(10); - let temp2 = p[i10].rotate_right(8); - p[i] = p[i] - .wrapping_add(temp2) - .wrapping_add(temp0 ^ temp1); - let temp3 = { - // The h1 function in HC-128 - let a = p[i12] as u8; - let c = (p[i12] >> 16) as u8; - q[a as usize].wrapping_add(q[256 + c as usize]) - }; - temp3 ^ p[i] - } - - // One step of HC-128, update Q and generate 32 bits keystream - // Similar to `step_p`, but `p` and `q` are swapped, and the rotates are to - // the left instead of to the right. - #[inline(always)] - fn step_q(&mut self, i: usize, i511: usize, i3: usize, i10: usize, i12: usize) -> u32 { - let (p, q) = self.t.split_at_mut(512); - let temp0 = q[i511].rotate_left(23); - let temp1 = q[i3].rotate_left(10); - let temp2 = q[i10].rotate_left(8); - q[i] = q - [i] - .wrapping_add(temp2) - .wrapping_add(temp0 ^ temp1); - let temp3 = { - // The h2 function in HC-128 - let a = q[i12] as u8; - let c = (q[i12] >> 16) as u8; - p[a as usize].wrapping_add(p[256 + c as usize]) - }; - temp3 ^ q[i] - } - - fn sixteen_steps(&mut self) { - assert!(self.counter1024 % 16 == 0); - - let cc = self.counter1024 % 512; - let dd = (cc + 16) % 512; - let ee = cc.wrapping_sub(16) % 512; - // These asserts let the compiler optimize out the bounds checks. - // Some of them may be superfluous, and that's fine: - // they'll be optimized out if that's the case. - assert!(ee + 15 < 512); - assert!(cc + 15 < 512); - assert!(dd < 512); - - if self.counter1024 < 512 { - // P block - self.t[cc+0] = self.step_p(cc+0, cc+1, ee+13, ee+6, ee+4); - self.t[cc+1] = self.step_p(cc+1, cc+2, ee+14, ee+7, ee+5); - self.t[cc+2] = self.step_p(cc+2, cc+3, ee+15, ee+8, ee+6); - self.t[cc+3] = self.step_p(cc+3, cc+4, cc+0, ee+9, ee+7); - self.t[cc+4] = self.step_p(cc+4, cc+5, cc+1, ee+10, ee+8); - self.t[cc+5] = self.step_p(cc+5, cc+6, cc+2, ee+11, ee+9); - self.t[cc+6] = self.step_p(cc+6, cc+7, cc+3, ee+12, ee+10); - self.t[cc+7] = self.step_p(cc+7, cc+8, cc+4, ee+13, ee+11); - self.t[cc+8] = self.step_p(cc+8, cc+9, cc+5, ee+14, ee+12); - self.t[cc+9] = self.step_p(cc+9, cc+10, cc+6, ee+15, ee+13); - self.t[cc+10] = self.step_p(cc+10, cc+11, cc+7, cc+0, ee+14); - self.t[cc+11] = self.step_p(cc+11, cc+12, cc+8, cc+1, ee+15); - self.t[cc+12] = self.step_p(cc+12, cc+13, cc+9, cc+2, cc+0); - self.t[cc+13] = self.step_p(cc+13, cc+14, cc+10, cc+3, cc+1); - self.t[cc+14] = self.step_p(cc+14, cc+15, cc+11, cc+4, cc+2); - self.t[cc+15] = self.step_p(cc+15, dd+0, cc+12, cc+5, cc+3); - } else { - // Q block - self.t[cc+512+0] = self.step_q(cc+0, cc+1, ee+13, ee+6, ee+4); - self.t[cc+512+1] = self.step_q(cc+1, cc+2, ee+14, ee+7, ee+5); - self.t[cc+512+2] = self.step_q(cc+2, cc+3, ee+15, ee+8, ee+6); - self.t[cc+512+3] = self.step_q(cc+3, cc+4, cc+0, ee+9, ee+7); - self.t[cc+512+4] = self.step_q(cc+4, cc+5, cc+1, ee+10, ee+8); - self.t[cc+512+5] = self.step_q(cc+5, cc+6, cc+2, ee+11, ee+9); - self.t[cc+512+6] = self.step_q(cc+6, cc+7, cc+3, ee+12, ee+10); - self.t[cc+512+7] = self.step_q(cc+7, cc+8, cc+4, ee+13, ee+11); - self.t[cc+512+8] = self.step_q(cc+8, cc+9, cc+5, ee+14, ee+12); - self.t[cc+512+9] = self.step_q(cc+9, cc+10, cc+6, ee+15, ee+13); - self.t[cc+512+10] = self.step_q(cc+10, cc+11, cc+7, cc+0, ee+14); - self.t[cc+512+11] = self.step_q(cc+11, cc+12, cc+8, cc+1, ee+15); - self.t[cc+512+12] = self.step_q(cc+12, cc+13, cc+9, cc+2, cc+0); - self.t[cc+512+13] = self.step_q(cc+13, cc+14, cc+10, cc+3, cc+1); - self.t[cc+512+14] = self.step_q(cc+14, cc+15, cc+11, cc+4, cc+2); - self.t[cc+512+15] = self.step_q(cc+15, dd+0, cc+12, cc+5, cc+3); - } - self.counter1024 += 16; - } - - // Initialize an HC-128 random number generator. The seed has to be - // 256 bits in length (`[u32; 8]`), matching the 128 bit `key` followed by - // 128 bit `iv` when HC-128 where to be used as a stream cipher. - #[inline(always)] // single use: SeedableRng::from_seed - fn init(seed: [u32; SEED_WORDS]) -> Self { - #[inline] - fn f1(x: u32) -> u32 { - x.rotate_right(7) ^ x.rotate_right(18) ^ (x >> 3) - } - - #[inline] - fn f2(x: u32) -> u32 { - x.rotate_right(17) ^ x.rotate_right(19) ^ (x >> 10) - } - - let mut t = [0u32; 1024]; - - // Expand the key and iv into P and Q - let (key, iv) = seed.split_at(4); - t[..4].copy_from_slice(key); - t[4..8].copy_from_slice(key); - t[8..12].copy_from_slice(iv); - t[12..16].copy_from_slice(iv); - - // Generate the 256 intermediate values W[16] ... W[256+16-1], and - // copy the last 16 generated values to the start op P. - for i in 16..256 + 16 { - t[i] = f2(t[i - 2]) - .wrapping_add(t[i - 7]) - .wrapping_add(f1(t[i - 15])) - .wrapping_add(t[i - 16]) - .wrapping_add(i as u32); - } - { - let (p1, p2) = t.split_at_mut(256); - p1[0..16].copy_from_slice(&p2[0..16]); - } - - // Generate both the P and Q tables - for i in 16..1024 { - t[i] = f2(t[i - 2]) - .wrapping_add(t[i - 7]) - .wrapping_add(f1(t[i - 15])) - .wrapping_add(t[i - 16]) - .wrapping_add(256 + i as u32); - } - - let mut core = Self { t, counter1024: 0 }; - - // run the cipher 1024 steps - for _ in 0..64 { - core.sixteen_steps() - } - core.counter1024 = 0; - core - } -} - -impl SeedableRng for Hc128Core { - type Seed = [u8; SEED_WORDS * 4]; - - /// Create an HC-128 random number generator with a seed. The seed has to be - /// 256 bits in length, matching the 128 bit `key` followed by 128 bit `iv` - /// when HC-128 where to be used as a stream cipher. - fn from_seed(seed: Self::Seed) -> Self { - let mut seed_u32 = [0u32; SEED_WORDS]; - le::read_u32_into(&seed, &mut seed_u32); - Self::init(seed_u32) - } -} - -impl CryptoRng for Hc128Core {} - -// Custom PartialEq implementation as it can't currently be derived from an array of size 1024 -impl PartialEq for Hc128Core { - fn eq(&self, rhs: &Self) -> bool { - &self.t[..] == &rhs.t[..] && self.counter1024 == rhs.counter1024 - } -} -impl Eq for Hc128Core {} - -#[cfg(test)] -mod test { - use super::Hc128Rng; - use ::rand_core::{RngCore, SeedableRng}; - - #[test] - // Test vector 1 from the paper "The Stream Cipher HC-128" - fn test_hc128_true_values_a() { - #[rustfmt::skip] - let seed = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // key - 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; // iv - let mut rng = Hc128Rng::from_seed(seed); - - let mut results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng.next_u32(); - } - #[rustfmt::skip] - let expected = [0x73150082, 0x3bfd03a0, 0xfb2fd77f, 0xaa63af0e, - 0xde122fc6, 0xa7dc29b6, 0x62a68527, 0x8b75ec68, - 0x9036db1e, 0x81896005, 0x00ade078, 0x491fbf9a, - 0x1cdc3013, 0x6c3d6e24, 0x90f664b2, 0x9cd57102]; - assert_eq!(results, expected); - } - - #[test] - // Test vector 2 from the paper "The Stream Cipher HC-128" - fn test_hc128_true_values_b() { - #[rustfmt::skip] - let seed = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // key - 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; // iv - let mut rng = Hc128Rng::from_seed(seed); - - let mut results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng.next_u32(); - } - #[rustfmt::skip] - let expected = [0xc01893d5, 0xb7dbe958, 0x8f65ec98, 0x64176604, - 0x36fc6724, 0xc82c6eec, 0x1b1c38a7, 0xc9b42a95, - 0x323ef123, 0x0a6a908b, 0xce757b68, 0x9f14f7bb, - 0xe4cde011, 0xaeb5173f, 0x89608c94, 0xb5cf46ca]; - assert_eq!(results, expected); - } - - #[test] - // Test vector 3 from the paper "The Stream Cipher HC-128" - fn test_hc128_true_values_c() { - #[rustfmt::skip] - let seed = [0x55,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // key - 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; // iv - let mut rng = Hc128Rng::from_seed(seed); - - let mut results = [0u32; 16]; - for i in results.iter_mut() { - *i = rng.next_u32(); - } - #[rustfmt::skip] - let expected = [0x518251a4, 0x04b4930a, 0xb02af931, 0x0639f032, - 0xbcb4a47a, 0x5722480b, 0x2bf99f72, 0xcdc0e566, - 0x310f0c56, 0xd3cc83e8, 0x663db8ef, 0x62dfe07f, - 0x593e1790, 0xc5ceaa9c, 0xab03806f, 0xc9a6e5a0]; - assert_eq!(results, expected); - } - - #[test] - fn test_hc128_true_values_u64() { - #[rustfmt::skip] - let seed = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // key - 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; // iv - let mut rng = Hc128Rng::from_seed(seed); - - let mut results = [0u64; 8]; - for i in results.iter_mut() { - *i = rng.next_u64(); - } - #[rustfmt::skip] - let expected = [0x3bfd03a073150082, 0xaa63af0efb2fd77f, - 0xa7dc29b6de122fc6, 0x8b75ec6862a68527, - 0x818960059036db1e, 0x491fbf9a00ade078, - 0x6c3d6e241cdc3013, 0x9cd5710290f664b2]; - assert_eq!(results, expected); - - // The RNG operates in a P block of 512 results and next a Q block. - // After skipping 2*800 u32 results we end up somewhere in the Q block - // of the second round - for _ in 0..800 { - rng.next_u64(); - } - - for i in results.iter_mut() { - *i = rng.next_u64(); - } - #[rustfmt::skip] - let expected = [0xd8c4d6ca84d0fc10, 0xf16a5d91dc66e8e7, - 0xd800de5bc37a8653, 0x7bae1f88c0dfbb4c, - 0x3bfe1f374e6d4d14, 0x424b55676be3fa06, - 0xe3a1e8758cbff579, 0x417f7198c5652bcd]; - assert_eq!(results, expected); - } - - #[test] - fn test_hc128_true_values_bytes() { - #[rustfmt::skip] - let seed = [0x55,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // key - 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; // iv - let mut rng = Hc128Rng::from_seed(seed); - #[rustfmt::skip] - let expected = [0x31, 0xf9, 0x2a, 0xb0, 0x32, 0xf0, 0x39, 0x06, - 0x7a, 0xa4, 0xb4, 0xbc, 0x0b, 0x48, 0x22, 0x57, - 0x72, 0x9f, 0xf9, 0x2b, 0x66, 0xe5, 0xc0, 0xcd, - 0x56, 0x0c, 0x0f, 0x31, 0xe8, 0x83, 0xcc, 0xd3, - 0xef, 0xb8, 0x3d, 0x66, 0x7f, 0xe0, 0xdf, 0x62, - 0x90, 0x17, 0x3e, 0x59, 0x9c, 0xaa, 0xce, 0xc5, - 0x6f, 0x80, 0x03, 0xab, 0xa0, 0xe5, 0xa6, 0xc9, - 0x60, 0x95, 0x84, 0x7a, 0xa5, 0x68, 0x5a, 0x84, - 0xea, 0xd5, 0xf3, 0xea, 0x73, 0xa9, 0xad, 0x01, - 0x79, 0x7d, 0xbe, 0x9f, 0xea, 0xe3, 0xf9, 0x74, - 0x0e, 0xda, 0x2f, 0xa0, 0xe4, 0x7b, 0x4b, 0x1b, - 0xdd, 0x17, 0x69, 0x4a, 0xfe, 0x9f, 0x56, 0x95, - 0xad, 0x83, 0x6b, 0x9d, 0x60, 0xa1, 0x99, 0x96, - 0x90, 0x00, 0x66, 0x7f, 0xfa, 0x7e, 0x65, 0xe9, - 0xac, 0x8b, 0x92, 0x34, 0x77, 0xb4, 0x23, 0xd0, - 0xb9, 0xab, 0xb1, 0x47, 0x7d, 0x4a, 0x13, 0x0a]; - - // Pick a somewhat large buffer so we can test filling with the - // remainder from `state.results`, directly filling the buffer, and - // filling the remainder of the buffer. - let mut buffer = [0u8; 16 * 4 * 2]; - // Consume a value so that we have a remainder. - assert!(rng.next_u64() == 0x04b4930a518251a4); - rng.fill_bytes(&mut buffer); - - // [u8; 128] doesn't implement PartialEq - assert_eq!(buffer.len(), expected.len()); - for (b, e) in buffer.iter().zip(expected.iter()) { - assert_eq!(b, e); - } - } - - #[test] - fn test_hc128_clone() { - #[rustfmt::skip] - let seed = [0x55,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // key - 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0]; // iv - let mut rng1 = Hc128Rng::from_seed(seed); - let mut rng2 = rng1.clone(); - for _ in 0..16 { - assert_eq!(rng1.next_u32(), rng2.next_u32()); - } - } -} diff --git a/rand_hc/src/lib.rs b/rand_hc/src/lib.rs deleted file mode 100644 index 995cb1d043d..00000000000 --- a/rand_hc/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! The HC128 random number generator. - -#![doc( - html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", - html_favicon_url = "https://www.rust-lang.org/favicon.ico", - html_root_url = "https://rust-random.github.io/rand/" -)] -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] -#![doc(test(attr(allow(unused_variables), deny(warnings))))] -#![no_std] - -mod hc128; - -pub use hc128::{Hc128Core, Hc128Rng}; From c831b8dd3b5eefef7adec4f0b02a2a5515b55164 Mon Sep 17 00:00:00 2001 From: Arif Driessen Date: Tue, 20 Jul 2021 08:38:13 +0200 Subject: [PATCH 144/443] Better random chars example --- src/distributions/other.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 0935d055b3e..fada05d3b4b 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -32,16 +32,11 @@ use std::mem::{self, MaybeUninit}; /// # Example /// /// ``` -/// use std::iter; /// use rand::{Rng, thread_rng}; /// use rand::distributions::Alphanumeric; /// /// let mut rng = thread_rng(); -/// let chars: String = iter::repeat(()) -/// .map(|()| rng.sample(Alphanumeric)) -/// .map(char::from) -/// .take(7) -/// .collect(); +/// let chars: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect(); /// println!("Random chars: {}", chars); /// ``` /// From e64df6631eb3be38e75b611bd5aa7a395552f642 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 17 Aug 2021 09:51:37 +0100 Subject: [PATCH 145/443] Alphanumeric: document usage with DistString trait --- src/distributions/other.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index fada05d3b4b..d0f107d9bd5 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -40,6 +40,13 @@ use std::mem::{self, MaybeUninit}; /// println!("Random chars: {}", chars); /// ``` /// +/// Alternatively, one can use the [`DistString`] trait: +/// ``` +/// use rand::distributions::{Alphanumeric, DistString}; +/// let string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); +/// println!("Random string: {}", string); +/// ``` +/// /// # Passwords /// /// Users sometimes ask whether it is safe to use a string of random characters From 32343a6f371dd99635bdd74119300e1eb8661a34 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 17 Aug 2021 09:55:53 +0100 Subject: [PATCH 146/443] Rearrange Cargo.toml --- Cargo.toml | 18 +++++++++--------- rand_core/Cargo.toml | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1597b1257d6..19cf619a0fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,15 @@ autobenches = true edition = "2018" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] +[package.metadata.docs.rs] +# To build locally: +# RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open +all-features = true +rustdoc-args = ["--cfg", "doc_cfg"] + +[package.metadata.playground] +features = ["small_rng", "serde1"] + [features] # Meta-features: default = ["std", "std_rng"] @@ -74,12 +83,3 @@ libc = { version = "0.2.22", optional = true, default-features = false } rand_pcg = { path = "rand_pcg", version = "0.3.0" } # Only to test serde1 bincode = "1.2.1" - -[package.metadata.docs.rs] -# To build locally: -# RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open -all-features = true -rustdoc-args = ["--cfg", "doc_cfg"] - -[package.metadata.playground] -features = ["small_rng", "serde1"] diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 6604bc5a8b1..c9ce4263326 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -14,15 +14,6 @@ keywords = ["random", "rng"] categories = ["algorithms", "no-std"] edition = "2018" -[features] -std = ["alloc", "getrandom", "getrandom/std"] # use std library; should be default but for above bug -alloc = [] # enables Vec and Box support without std -serde1 = ["serde"] # enables serde for BlockRng wrapper - -[dependencies] -serde = { version = "1", features = ["derive"], optional = true } -getrandom = { version = "0.2", optional = true } - [package.metadata.docs.rs] # To build locally: # RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open @@ -31,3 +22,12 @@ rustdoc-args = ["--cfg", "doc_cfg"] [package.metadata.playground] all-features = true + +[features] +std = ["alloc", "getrandom", "getrandom/std"] # use std library; should be default but for above bug +alloc = [] # enables Vec and Box support without std +serde1 = ["serde"] # enables serde for BlockRng wrapper + +[dependencies] +serde = { version = "1", features = ["derive"], optional = true } +getrandom = { version = "0.2", optional = true } From 1996707ac23d43bc56c5c86b32f32fbbf9b4cfba Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 17 Aug 2021 15:34:25 +0100 Subject: [PATCH 147/443] Review feedback --- src/distributions/other.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index d0f107d9bd5..4c2471e6273 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -40,7 +40,8 @@ use std::mem::{self, MaybeUninit}; /// println!("Random chars: {}", chars); /// ``` /// -/// Alternatively, one can use the [`DistString`] trait: +/// The [`DistString`] trait provides an easier method of generating +/// a random `String`, and offers more efficient allocation: /// ``` /// use rand::distributions::{Alphanumeric, DistString}; /// let string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); From 9b9a89f0557b2a3d701e3cd3c37ce35773d7d494 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Aug 2021 20:20:42 +0200 Subject: [PATCH 148/443] Fix unsoundness in `::next_u32` Many thanks to @joshlf for reporting this issue. Fixes #1158. --- rand_core/src/block.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 5f48bfaf546..08929896164 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -352,27 +352,22 @@ where { #[inline] fn next_u32(&mut self) -> u32 { - let mut index = self.index * 2 - self.half_used as usize; - if index >= self.results.as_ref().len() * 2 { + if self.index >= self.results.as_ref().len() { self.core.generate(&mut self.results); self.index = 0; // `self.half_used` is by definition `false` self.half_used = false; - index = 0; } self.half_used = !self.half_used; self.index += self.half_used as usize; - // Index as if this is a u32 slice. - unsafe { - let results = &*(self.results.as_ref() as *const [u64] as *const [u32]); - if cfg!(target_endian = "little") { - *results.get_unchecked(index) - } else { - *results.get_unchecked(index ^ 1) - } - } + let half_used = if cfg!(target_endian = "little") { + self.half_used + } else { + !self.half_used + }; + (self.results.as_ref()[self.index] >> (32 * (half_used as usize))) as u32 } #[inline] From e85a8759aa58e1520f38e85a18a5bda9ce3c19a0 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Aug 2021 20:35:45 +0200 Subject: [PATCH 149/443] rand_core: Update changelog --- rand_core/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index 82c830086cd..c95e60deefc 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.4] - unreleased +### Fixed +- Fix unsoundness in `::next_u32` (#1160) + ## [0.6.3] - 2021-06-15 ### Changed - Improved bound for `serde` impls on `BlockRng` (#1130) From 536e18b0ea571b4b9f504ec2d9561b23f647ab8f Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Aug 2021 20:45:09 +0200 Subject: [PATCH 150/443] rand: Update changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e87add21bc..e33d68f58a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,14 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [0.8.5] - unreleased ### Fixes -- Fix build on non-32/64-bit architectures (#1144) +- Fix build on non-32/64-bit architectures (#1144) + +### Platform support +- Remove special cases for emscripten (#1142) + +### Documentation +- Added docs about rand's use of const generics (#1150) +- Better random chars example (#1157) ## [0.8.4] - 2021-06-15 ### Additions From db6e81f34e2503bec90c531122469775720fc6f8 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Aug 2021 20:49:50 +0200 Subject: [PATCH 151/443] Bump versions --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- rand_core/CHANGELOG.md | 2 +- rand_core/Cargo.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e33d68f58a9..e2c2bf715c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. -## [0.8.5] - unreleased +## [0.8.5] - 2021-08-20 ### Fixes - Fix build on non-32/64-bit architectures (#1144) diff --git a/Cargo.toml b/Cargo.toml index 19cf619a0fe..37719112fb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.8.4" +version = "0.8.5" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index c95e60deefc..17482d40887 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.6.4] - unreleased +## [0.6.4] - 2021-08-20 ### Fixed - Fix unsoundness in `::next_u32` (#1160) diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index c9ce4263326..bfaa029bada 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.6.3" +version = "0.6.4" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From 659a12d89b6e89931b6649ff08375d9a49873e6d Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 19 Aug 2021 20:55:30 +0200 Subject: [PATCH 152/443] rand: Fix clippy warnings --- src/distributions/distribution.rs | 2 +- src/seq/mod.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index e7f7677a85a..a516a906bda 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -231,7 +231,7 @@ mod tests { let mut rng = crate::test::rng(212); let val = dist.sample(&mut rng); - assert!(val >= 15 && val <= 20); + assert!((15..=20).contains(&val)); } #[test] diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 9eeb77749c9..069e9e6b19e 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -1252,11 +1252,10 @@ mod test { // Case 2: All of the weights are 0 let choices = [('a', 0), ('b', 0), ('c', 0)]; - let result = choices + + assert_eq!(choices .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap() - .collect::>(); - assert_eq!(result.len(), 2); + .unwrap().count(), 2); // Case 3: Negative weights let choices = [('a', -1), ('b', 1), ('c', 1)]; @@ -1269,11 +1268,9 @@ mod test { // Case 4: Empty list let choices = []; - let result = choices + assert_eq!(choices .choose_multiple_weighted(&mut rng, 0, |_: &()| 0) - .unwrap() - .collect::>(); - assert_eq!(result.len(), 0); + .unwrap().count(), 0); // Case 5: NaN weights let choices = [('a', core::f64::NAN), ('b', 1.0), ('c', 1.0)]; From e73fe3a4bc8f02821138c312d2d29dff0ba34995 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 23 Aug 2021 17:49:59 +0200 Subject: [PATCH 153/443] Simplify cfg logic --- rand_core/src/block.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 08929896164..6b4a7c87093 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -362,11 +362,11 @@ where self.half_used = !self.half_used; self.index += self.half_used as usize; - let half_used = if cfg!(target_endian = "little") { - self.half_used - } else { - !self.half_used - }; + #[cfg(target_endian = "little")] + let half_used = self.half_used; + #[cfg(not(target_endian = "little"))] + let half_used = !self.half_used; + (self.results.as_ref()[self.index] >> (32 * (half_used as usize))) as u32 } From b86ec29224b648f3f55c3325558ebccc9cf81f79 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 23 Aug 2021 17:55:18 +0200 Subject: [PATCH 154/443] BlockRng::next_u32: Make sure to use half-entry at the previous index --- rand_core/src/block.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 6b4a7c87093..cd543c37c73 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -372,12 +372,14 @@ where #[inline] fn next_u64(&mut self) -> u64 { - if self.index >= self.results.as_ref().len() { + let mut index = self.index - self.half_used as usize; + if index >= self.results.as_ref().len() { self.core.generate(&mut self.results); self.index = 0; + index = 0; } - let value = self.results.as_ref()[self.index]; + let value = self.results.as_ref()[index]; self.index += 1; self.half_used = false; value From 1a3c2a9a3b95d7f52bc5e737caf3a47bc725840b Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 23 Aug 2021 18:25:17 +0200 Subject: [PATCH 155/443] rand_chacha: Test that `next_u32` and `next_u64` can be mixed --- rand_chacha/src/chacha.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 50da81bfafe..85e826bd763 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -629,4 +629,29 @@ mod test { rng.set_word_pos(0); assert_eq!(rng.get_word_pos(), 0); } + + #[test] + fn test_chacha_next_u32_vs_next_u64() { + let mut rng1 = ChaChaRng::from_seed(Default::default()); + let mut rng2 = rng1.clone(); + let mut rng3 = rng1.clone(); + + + let mut a = [0; 16]; + (&mut a[..4]).copy_from_slice(&rng1.next_u32().to_le_bytes()); + (&mut a[4..12]).copy_from_slice(&rng1.next_u64().to_le_bytes()); + (&mut a[12..]).copy_from_slice(&rng1.next_u32().to_le_bytes()); + + let mut b = [0; 16]; + (&mut b[..4]).copy_from_slice(&rng2.next_u32().to_le_bytes()); + (&mut b[4..8]).copy_from_slice(&rng2.next_u32().to_le_bytes()); + (&mut b[8..]).copy_from_slice(&rng2.next_u64().to_le_bytes()); + assert_eq!(a, b); + + let mut c = [0; 16]; + (&mut c[..8]).copy_from_slice(&rng3.next_u64().to_le_bytes()); + (&mut c[8..12]).copy_from_slice(&rng3.next_u32().to_le_bytes()); + (&mut c[12..]).copy_from_slice(&rng3.next_u32().to_le_bytes()); + assert_eq!(a, b); + } } From d5dbf47c256fd6ef302a0dfba5f36b9d3baec8bf Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 24 Aug 2021 17:04:10 +0200 Subject: [PATCH 156/443] Fix `BlockRng::next_*` logic --- rand_core/src/block.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index cd543c37c73..666c45072bb 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -352,9 +352,11 @@ where { #[inline] fn next_u32(&mut self) -> u32 { - if self.index >= self.results.as_ref().len() { + let mut index = self.index - self.half_used as usize; + if index >= self.results.as_ref().len() { self.core.generate(&mut self.results); self.index = 0; + index = 0; // `self.half_used` is by definition `false` self.half_used = false; } @@ -367,19 +369,17 @@ where #[cfg(not(target_endian = "little"))] let half_used = !self.half_used; - (self.results.as_ref()[self.index] >> (32 * (half_used as usize))) as u32 + (self.results.as_ref()[index] >> (32 * (half_used as usize))) as u32 } #[inline] fn next_u64(&mut self) -> u64 { - let mut index = self.index - self.half_used as usize; - if index >= self.results.as_ref().len() { + if self.index >= self.results.as_ref().len() { self.core.generate(&mut self.results); self.index = 0; - index = 0; } - let value = self.results.as_ref()[index]; + let value = self.results.as_ref()[self.index]; self.index += 1; self.half_used = false; value From eb1de23ff54400998aefd0529088e5cab2f8d677 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 24 Aug 2021 18:09:00 +0200 Subject: [PATCH 157/443] Fix spelling --- rand_chacha/src/chacha.rs | 2 +- rand_core/src/error.rs | 2 +- rand_core/src/lib.rs | 2 +- rand_distr/src/binomial.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 50da81bfafe..c7150127b3c 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -182,7 +182,7 @@ macro_rules! chacha_impl { impl $ChaChaXRng { // The buffer is a 4-block window, i.e. it is always at a block-aligned position in the - // stream but if the stream has been seeked it may not be self-aligned. + // stream but if the stream has been sought it may not be self-aligned. /// Get the offset from the start of the stream, in 32-bit words. /// diff --git a/rand_core/src/error.rs b/rand_core/src/error.rs index a64c430da8b..411896f2c47 100644 --- a/rand_core/src/error.rs +++ b/rand_core/src/error.rs @@ -82,7 +82,7 @@ impl Error { /// /// This method is identical to `std::io::Error::raw_os_error()`, except /// that it works in `no_std` contexts. If this method returns `None`, the - /// error value can still be formatted via the `Diplay` implementation. + /// error value can still be formatted via the `Display` implementation. #[inline] pub fn raw_os_error(&self) -> Option { #[cfg(feature = "std")] diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index bc24270771b..d03288c4e50 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -196,7 +196,7 @@ pub trait RngCore { /// Some generators may satisfy an additional property, however this is not /// required by this trait: if the CSPRNG's state is revealed, it should not be /// computationally-feasible to reconstruct output prior to this. Some other -/// generators allow backwards-computation and are consided *reversible*. +/// generators allow backwards-computation and are considered *reversible*. /// /// Note that this trait is provided for guidance only and cannot guarantee /// suitability for cryptographic applications. In general it should only be diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 5efe367e126..14ae5aa6c79 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -73,7 +73,7 @@ impl Binomial { } } -/// Convert a `f64` to an `i64`, panicing on overflow. +/// Convert a `f64` to an `i64`, panicking on overflow. // In the future (Rust 1.34), this might be replaced with `TryFrom`. fn f64_to_i64(x: f64) -> i64 { assert!(x < (core::i64::MAX as f64)); From 8bdee0d46458dd28af4ddae78d46f5cf08ada380 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 24 Aug 2021 18:14:12 +0200 Subject: [PATCH 158/443] Remove outdated comment `TryFrom` does not support floats. --- rand_distr/src/binomial.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 14ae5aa6c79..8e7513a2477 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -74,7 +74,6 @@ impl Binomial { } /// Convert a `f64` to an `i64`, panicking on overflow. -// In the future (Rust 1.34), this might be replaced with `TryFrom`. fn f64_to_i64(x: f64) -> i64 { assert!(x < (core::i64::MAX as f64)); x as i64 From 564055aed9f0620a71179e64232946c8160a7828 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 24 Aug 2021 20:39:31 +0200 Subject: [PATCH 159/443] Remove notice by Apache License Authors for all other crates --- rand_chacha/LICENSE-APACHE | 25 ------------------------- rand_core/LICENSE-APACHE | 14 -------------- rand_distr/LICENSE-APACHE | 14 -------------- rand_hc/LICENSE-APACHE | 14 -------------- rand_pcg/LICENSE-APACHE | 14 -------------- 5 files changed, 81 deletions(-) diff --git a/rand_chacha/LICENSE-APACHE b/rand_chacha/LICENSE-APACHE index 17d74680f8c..494ad3bfdfe 100644 --- a/rand_chacha/LICENSE-APACHE +++ b/rand_chacha/LICENSE-APACHE @@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/rand_core/LICENSE-APACHE b/rand_core/LICENSE-APACHE index 17d74680f8c..455787c2334 100644 --- a/rand_core/LICENSE-APACHE +++ b/rand_core/LICENSE-APACHE @@ -185,17 +185,3 @@ APPENDIX: How to apply the Apache License to your work. file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/rand_distr/LICENSE-APACHE b/rand_distr/LICENSE-APACHE index 17d74680f8c..455787c2334 100644 --- a/rand_distr/LICENSE-APACHE +++ b/rand_distr/LICENSE-APACHE @@ -185,17 +185,3 @@ APPENDIX: How to apply the Apache License to your work. file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/rand_hc/LICENSE-APACHE b/rand_hc/LICENSE-APACHE index 17d74680f8c..455787c2334 100644 --- a/rand_hc/LICENSE-APACHE +++ b/rand_hc/LICENSE-APACHE @@ -185,17 +185,3 @@ APPENDIX: How to apply the Apache License to your work. file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/rand_pcg/LICENSE-APACHE b/rand_pcg/LICENSE-APACHE index 17d74680f8c..455787c2334 100644 --- a/rand_pcg/LICENSE-APACHE +++ b/rand_pcg/LICENSE-APACHE @@ -185,17 +185,3 @@ APPENDIX: How to apply the Apache License to your work. file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. From 36c16383f8cca13b7581f611417cff99a78c9052 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 24 Aug 2021 20:44:31 +0200 Subject: [PATCH 160/443] Fix more spelling mistakes --- rand_core/src/lib.rs | 2 +- rand_distr/src/geometric.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index d03288c4e50..1ae692cc701 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -215,7 +215,7 @@ pub trait CryptoRng {} /// /// [`rand`]: https://docs.rs/rand pub trait SeedableRng: Sized { - /// Seed type, which is restricted to types mutably-dereferencable as `u8` + /// Seed type, which is restricted to types mutably-dereferenceable as `u8` /// arrays (we recommend `[u8; N]` for some `N`). /// /// It is recommended to seed PRNGs with a seed of at least circa 100 bits, diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 31bf98c896e..7988261a380 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -55,7 +55,7 @@ impl std::error::Error for Error {} impl Geometric { /// Construct a new `Geometric` with the given shape parameter `p` - /// (probablity of success on each trial). + /// (probability of success on each trial). pub fn new(p: f64) -> Result { if !p.is_finite() || p < 0.0 || p > 1.0 { Err(Error::InvalidProbability) From 693d3c1f0a8922d91482479dafb7a8651ae4db76 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 26 Aug 2021 14:07:38 +0200 Subject: [PATCH 161/443] Fix test --- rand_chacha/src/chacha.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 85e826bd763..5798b7ac3bf 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -636,7 +636,6 @@ mod test { let mut rng2 = rng1.clone(); let mut rng3 = rng1.clone(); - let mut a = [0; 16]; (&mut a[..4]).copy_from_slice(&rng1.next_u32().to_le_bytes()); (&mut a[4..12]).copy_from_slice(&rng1.next_u64().to_le_bytes()); @@ -652,6 +651,6 @@ mod test { (&mut c[..8]).copy_from_slice(&rng3.next_u64().to_le_bytes()); (&mut c[8..12]).copy_from_slice(&rng3.next_u32().to_le_bytes()); (&mut c[12..]).copy_from_slice(&rng3.next_u32().to_le_bytes()); - assert_eq!(a, b); + assert_eq!(a, c); } } From 650783831e300548e51df04dec149bb3d64f4eb3 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sat, 28 Aug 2021 14:34:45 -0500 Subject: [PATCH 162/443] Implement Gumbel --- rand_distr/src/gumbel.rs | 132 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 rand_distr/src/gumbel.rs diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs new file mode 100644 index 00000000000..4016dec0731 --- /dev/null +++ b/rand_distr/src/gumbel.rs @@ -0,0 +1,132 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Gumbel distribution. + +use crate::{Distribution, OpenClosed01}; +use core::fmt; +use num_traits::Float; +use rand::Rng; + +/// Samples floating-point numbers according to the Gumbel distribution +/// +/// # Example +/// ``` +/// use rand::prelude::*; +/// use rand_distr::Gumbel; +/// +/// let val: f64 = thread_rng().sample(Gumbel::new(1., 10.).unwrap()); +/// println!("{}", val); +/// ``` +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Gumbel +where + F: Float, + OpenClosed01: Distribution, +{ + location: F, + scale: F, +} + +/// Error type returned from `Gumbel::new`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// location is infinite or NaN + LocationNotValid, + /// scale is not finite positive number + ScaleNotValid, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::ScaleNotValid => "scale is not positive and finite in Gumbel distribution", + Error::LocationNotValid => "location is not finite in Gumbel distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + +impl Gumbel +where + F: Float, + OpenClosed01: Distribution, +{ + /// Construct a new `Gumbel` distribution with given `location` and `scale`. + pub fn new(location: F, scale: F) -> Result, Error> { + if scale <= F::zero() || scale.is_infinite() || scale.is_nan() { + return Err(Error::ScaleNotValid); + } + if location.is_infinite() || location.is_nan() { + return Err(Error::LocationNotValid); + } + Ok(Gumbel { location, scale }) + } +} + +impl Distribution for Gumbel +where + F: Float, + OpenClosed01: Distribution, +{ + fn sample(&self, rng: &mut R) -> F { + let x: F = rng.sample(OpenClosed01); + self.location - self.scale * (-(x).ln()).ln() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn test_zero_scale() { + Gumbel::new(0.0, 0.0).unwrap(); + } + + #[test] + #[should_panic] + fn test_infinite_scale() { + Gumbel::new(0.0, std::f64::INFINITY).unwrap(); + } + + #[test] + #[should_panic] + fn test_nan_scale() { + Gumbel::new(0.0, std::f64::NAN).unwrap(); + } + + #[test] + #[should_panic] + fn test_infinite_location() { + Gumbel::new(std::f64::INFINITY, 1.0).unwrap(); + } + + #[test] + #[should_panic] + fn test_nan_location() { + Gumbel::new(std::f64::NAN, 1.0).unwrap(); + } + + #[test] + fn test_sample_against_cdf() { + let scale = 1.0; + let shape = 2.0; + let d = Gumbel::new(scale, shape).unwrap(); + let mut rng = crate::test::rng(1); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 0.); + } + } +} From f2621e761e5217415771f7c1daa223d3fc58d015 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sat, 28 Aug 2021 14:38:22 -0500 Subject: [PATCH 163/443] fixup --- rand_distr/src/gumbel.rs | 2 +- rand_distr/src/lib.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 4016dec0731..29edbddd603 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -20,7 +20,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Gumbel; /// -/// let val: f64 = thread_rng().sample(Gumbel::new(1., 10.).unwrap()); +/// let val: f64 = thread_rng().sample(Gumbel::new(0.0, 1.0).unwrap()); /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug)] diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 4b9e803a130..2f4125e0deb 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -77,6 +77,7 @@ //! - Misc. distributions //! - [`InverseGaussian`] distribution //! - [`NormalInverseGaussian`] distribution +//! - [`Gumbel`] distribution #[cfg(feature = "alloc")] extern crate alloc; @@ -104,6 +105,7 @@ pub use self::gamma::{ Gamma, StudentT, }; pub use self::geometric::{Error as GeoError, Geometric, StandardGeometric}; +pub use self::gumbel::{Error as GumbelError, Gumbel}; pub use self::hypergeometric::{Error as HyperGeoError, Hypergeometric}; pub use self::inverse_gaussian::{InverseGaussian, Error as InverseGaussianError}; pub use self::normal::{Error as NormalError, LogNormal, Normal, StandardNormal}; @@ -186,6 +188,7 @@ mod dirichlet; mod exponential; mod gamma; mod geometric; +mod gumbel; mod hypergeometric; mod inverse_gaussian; mod normal; From f66762418a660768080f207c0f78541bc6830d2c Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sun, 29 Aug 2021 09:50:18 -0500 Subject: [PATCH 164/443] Add tests --- rand_distr/src/gumbel.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 29edbddd603..9981e10a50d 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -87,6 +87,7 @@ where #[cfg(test)] mod tests { use super::*; + use std::vec; #[test] #[should_panic] @@ -120,13 +121,32 @@ mod tests { #[test] fn test_sample_against_cdf() { + fn neg_log_log(x: f64) -> f64 { + -(-x.ln()).ln() + } + let location = 0.0; let scale = 1.0; - let shape = 2.0; - let d = Gumbel::new(scale, shape).unwrap(); + let iterations = 100_000; + let increment = 1.0 / iterations as f64; + let probabilities = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]; + let quantiles = probabilities + .iter() + .map(|x| neg_log_log(*x)) + .collect::>(); + let mut proportions = vec![0.0; 9]; + let d = Gumbel::new(location, scale).unwrap(); let mut rng = crate::test::rng(1); - for _ in 0..1000 { - let r = d.sample(&mut rng); - assert!(r >= 0.); + for _ in 0..iterations { + let replicate = d.sample(&mut rng); + for (i, q) in quantiles.iter().enumerate() { + if replicate < *q { + proportions[i] += increment; + } + } } + assert!(proportions + .iter() + .zip(&probabilities) + .all(|(p_hat, p)| (p_hat - p).abs() < 0.003)) } } From bd1d12913b0c6daeccb31e09dfb1dd56e8e2b880 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sun, 29 Aug 2021 09:54:01 -0500 Subject: [PATCH 165/443] Clean --- rand_distr/src/gumbel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 9981e10a50d..eca2b691dae 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -80,7 +80,7 @@ where { fn sample(&self, rng: &mut R) -> F { let x: F = rng.sample(OpenClosed01); - self.location - self.scale * (-(x).ln()).ln() + self.location - self.scale * (-x.ln()).ln() } } From 6708a13ed0ae7a0f1713058e078e46a35170faa0 Mon Sep 17 00:00:00 2001 From: Daniel Saxton <2658661+dsaxton@users.noreply.github.com> Date: Sun, 29 Aug 2021 13:34:54 -0500 Subject: [PATCH 166/443] Update rand_distr/src/gumbel.rs Co-authored-by: Vinzent Steinberg --- rand_distr/src/gumbel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index eca2b691dae..3149ae65b08 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Developers of the Rand project. +// Copyright 2021 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license From 106fb361edea70ecb1cf29b272c8c75c500a8be6 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sun, 29 Aug 2021 13:37:36 -0500 Subject: [PATCH 167/443] Update Error enum --- rand_distr/src/gumbel.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 3149ae65b08..cb97bfe5113 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -38,16 +38,16 @@ where #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// location is infinite or NaN - LocationNotValid, + LocationNotFinite, /// scale is not finite positive number - ScaleNotValid, + ScaleNotPositive, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - Error::ScaleNotValid => "scale is not positive and finite in Gumbel distribution", - Error::LocationNotValid => "location is not finite in Gumbel distribution", + Error::ScaleNotPositive => "scale is not positive and finite in Gumbel distribution", + Error::LocationNotFinite => "location is not finite in Gumbel distribution", }) } } @@ -64,10 +64,10 @@ where /// Construct a new `Gumbel` distribution with given `location` and `scale`. pub fn new(location: F, scale: F) -> Result, Error> { if scale <= F::zero() || scale.is_infinite() || scale.is_nan() { - return Err(Error::ScaleNotValid); + return Err(Error::ScaleNotPositive); } if location.is_infinite() || location.is_nan() { - return Err(Error::LocationNotValid); + return Err(Error::LocationNotFinite); } Ok(Gumbel { location, scale }) } From 0342ca9e61f6292f76282eec1975a3e284675fa5 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sun, 29 Aug 2021 13:48:11 -0500 Subject: [PATCH 168/443] Move distribution --- rand_distr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 2f4125e0deb..46860e31d06 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -56,6 +56,7 @@ //! - [`Poisson`] distribution //! - [`Exp`]onential distribution, and [`Exp1`] as a primitive //! - [`Weibull`] distribution +//! - [`Gumbel`] distribution //! - [`Zeta`] distribution //! - [`Zipf`] distribution //! - Gamma and derived distributions: @@ -77,7 +78,6 @@ //! - Misc. distributions //! - [`InverseGaussian`] distribution //! - [`NormalInverseGaussian`] distribution -//! - [`Gumbel`] distribution #[cfg(feature = "alloc")] extern crate alloc; From df1d64c366dbdda55b6f4686bc06e0b2163c9fc2 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sun, 29 Aug 2021 13:55:26 -0500 Subject: [PATCH 169/443] Add density --- rand_distr/src/gumbel.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index cb97bfe5113..5fe42a6b6f2 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -15,6 +15,9 @@ use rand::Rng; /// Samples floating-point numbers according to the Gumbel distribution /// +/// This distribution has a density function: +/// `f(x) = exp(-(z + exp(-z))) / σ`, where `z = (x - μ) / σ` +/// /// # Example /// ``` /// use rand::prelude::*; From 0ca44138171be499c98f2ffff67852469f3ebc2b Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sun, 29 Aug 2021 13:57:57 -0500 Subject: [PATCH 170/443] Try fix tests --- rand_distr/src/gumbel.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 5fe42a6b6f2..99ff36d185d 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -101,25 +101,25 @@ mod tests { #[test] #[should_panic] fn test_infinite_scale() { - Gumbel::new(0.0, std::f64::INFINITY).unwrap(); + Gumbel::new(0.0, core::f64::INFINITY).unwrap(); } #[test] #[should_panic] fn test_nan_scale() { - Gumbel::new(0.0, std::f64::NAN).unwrap(); + Gumbel::new(0.0, core::f64::NAN).unwrap(); } #[test] #[should_panic] fn test_infinite_location() { - Gumbel::new(std::f64::INFINITY, 1.0).unwrap(); + Gumbel::new(core::f64::INFINITY, 1.0).unwrap(); } #[test] #[should_panic] fn test_nan_location() { - Gumbel::new(std::f64::NAN, 1.0).unwrap(); + Gumbel::new(core::f64::NAN, 1.0).unwrap(); } #[test] From 0348413f8e07920449eff3b6163465692ecbe390 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sun, 29 Aug 2021 14:14:30 -0500 Subject: [PATCH 171/443] Nitpick grammar --- rand_distr/src/gumbel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 99ff36d185d..d7dd8bf1857 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -15,7 +15,7 @@ use rand::Rng; /// Samples floating-point numbers according to the Gumbel distribution /// -/// This distribution has a density function: +/// This distribution has density function: /// `f(x) = exp(-(z + exp(-z))) / σ`, where `z = (x - μ) / σ` /// /// # Example From 815de9127a9fb5674bfb5c7b964dd20347999d0e Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Sun, 29 Aug 2021 21:53:07 -0500 Subject: [PATCH 172/443] Try using alloc --- rand_distr/src/gumbel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index d7dd8bf1857..529822e4f14 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -90,7 +90,7 @@ where #[cfg(test)] mod tests { use super::*; - use std::vec; + use alloc::vec; #[test] #[should_panic] From 1f8716a27ebb0dc3ac4c4270ccbe8f892e455b38 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Mon, 30 Aug 2021 16:19:47 -0500 Subject: [PATCH 173/443] Use array --- rand_distr/src/gumbel.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 529822e4f14..10301e14e6a 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -90,7 +90,6 @@ where #[cfg(test)] mod tests { use super::*; - use alloc::vec; #[test] #[should_panic] @@ -132,11 +131,11 @@ mod tests { let iterations = 100_000; let increment = 1.0 / iterations as f64; let probabilities = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]; - let quantiles = probabilities - .iter() - .map(|x| neg_log_log(*x)) - .collect::>(); - let mut proportions = vec![0.0; 9]; + let mut quantiles = [0.0; 9]; + for (i, p) in probabilities.iter().enumerate() { + quantiles[i] = neg_log_log(*p); + } + let mut proportions = [0.0; 9]; let d = Gumbel::new(location, scale).unwrap(); let mut rng = crate::test::rng(1); for _ in 0..iterations { From 6f531c59298c9bc20f93902c68016f2e4f66da09 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Mon, 30 Aug 2021 16:30:06 -0500 Subject: [PATCH 174/443] Update doc --- rand_distr/src/gumbel.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 10301e14e6a..2b2019457c0 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -16,7 +16,8 @@ use rand::Rng; /// Samples floating-point numbers according to the Gumbel distribution /// /// This distribution has density function: -/// `f(x) = exp(-(z + exp(-z))) / σ`, where `z = (x - μ) / σ` +/// `f(x) = exp(-(z + exp(-z))) / σ`, where `z = (x - μ) / σ`, +/// `μ` is the location parameter, and `σ` the scale parameter. /// /// # Example /// ``` From 4dae4c3023c56f45352eba42de8a1811e0f6cb84 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Tue, 31 Aug 2021 16:40:11 -0500 Subject: [PATCH 175/443] Implement Frechet --- rand_distr/src/frechet.rs | 185 ++++++++++++++++++++++++++++++++++++++ rand_distr/src/lib.rs | 3 + 2 files changed, 188 insertions(+) create mode 100644 rand_distr/src/frechet.rs diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs new file mode 100644 index 00000000000..3f79f2119a6 --- /dev/null +++ b/rand_distr/src/frechet.rs @@ -0,0 +1,185 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Fréchet distribution. + +use crate::{Distribution, OpenClosed01}; +use core::fmt; +use num_traits::Float; +use rand::Rng; + +/// Samples floating-point numbers according to the Fréchet distribution +/// +/// This distribution has density function: +/// `f(x) = [(x - μ) / σ]^(-1 - α)` exp[-(x - μ) / σ]^(-α) α / σ , +/// where `μ` is the location parameter, `σ` the scale parameter, and `α` the shape parameter. +/// +/// # Example +/// ``` +/// use rand::prelude::*; +/// use rand_distr::Frechet; +/// +/// let val: f64 = thread_rng().sample(Frechet::new(0.0, 1.0, 1.0).unwrap()); +/// println!("{}", val); +/// ``` +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Frechet +where + F: Float, + OpenClosed01: Distribution, +{ + location: F, + scale: F, + shape: F, +} + +/// Error type returned from `Frechet::new`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// location is infinite or NaN + LocationNotFinite, + /// scale is not finite positive number + ScaleNotPositive, + /// shape is not finite positive number + ShapeNotPositive, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::LocationNotFinite => "location is not finite in Frechet distribution", + Error::ScaleNotPositive => "scale is not positive and finite in Frechet distribution", + Error::ShapeNotPositive => "shape is not positive and finite in Frechet distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + +impl Frechet +where + F: Float, + OpenClosed01: Distribution, +{ + /// Construct a new `Frechet` distribution with given `location`, `scale`, and `shape`. + pub fn new(location: F, scale: F, shape: F) -> Result, Error> { + if scale <= F::zero() || scale.is_infinite() || scale.is_nan() { + return Err(Error::ScaleNotPositive); + } + if shape <= F::zero() || shape.is_infinite() || shape.is_nan() { + return Err(Error::ShapeNotPositive); + } + if location.is_infinite() || location.is_nan() { + return Err(Error::LocationNotFinite); + } + Ok(Frechet { + location, + scale, + shape, + }) + } +} + +impl Distribution for Frechet +where + F: Float, + OpenClosed01: Distribution, +{ + fn sample(&self, rng: &mut R) -> F { + let x: F = rng.sample(OpenClosed01); + self.location + self.scale * (-x.ln()).powf(-self.shape.recip()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn test_zero_scale() { + Frechet::new(0.0, 0.0, 1.0).unwrap(); + } + + #[test] + #[should_panic] + fn test_infinite_scale() { + Frechet::new(0.0, core::f64::INFINITY, 1.0).unwrap(); + } + + #[test] + #[should_panic] + fn test_nan_scale() { + Frechet::new(0.0, core::f64::NAN, 1.0).unwrap(); + } + + #[test] + #[should_panic] + fn test_zero_shape() { + Frechet::new(0.0, 1.0, 0.0).unwrap(); + } + + #[test] + #[should_panic] + fn test_infinite_shape() { + Frechet::new(0.0, 1.0, core::f64::INFINITY).unwrap(); + } + + #[test] + #[should_panic] + fn test_nan_shape() { + Frechet::new(0.0, 1.0, core::f64::NAN).unwrap(); + } + + #[test] + #[should_panic] + fn test_infinite_location() { + Frechet::new(core::f64::INFINITY, 1.0, 1.0).unwrap(); + } + + #[test] + #[should_panic] + fn test_nan_location() { + Frechet::new(core::f64::NAN, 1.0, 1.0).unwrap(); + } + + #[test] + fn test_sample_against_cdf() { + fn quantile_function(x: f64) -> f64 { + (-x.ln()).recip() + } + let location = 0.0; + let scale = 1.0; + let shape = 1.0; + let iterations = 100_000; + let increment = 1.0 / iterations as f64; + let probabilities = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]; + let mut quantiles = [0.0; 9]; + for (i, p) in probabilities.iter().enumerate() { + quantiles[i] = quantile_function(*p); + } + let mut proportions = [0.0; 9]; + let d = Frechet::new(location, scale, shape).unwrap(); + let mut rng = crate::test::rng(1); + for _ in 0..iterations { + let replicate = d.sample(&mut rng); + for (i, q) in quantiles.iter().enumerate() { + if replicate < *q { + proportions[i] += increment; + } + } + } + assert!(proportions + .iter() + .zip(&probabilities) + .all(|(p_hat, p)| (p_hat - p).abs() < 0.003)) + } +} diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 46860e31d06..a4aded932af 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -57,6 +57,7 @@ //! - [`Exp`]onential distribution, and [`Exp1`] as a primitive //! - [`Weibull`] distribution //! - [`Gumbel`] distribution +//! - [`Frechet`] distribution //! - [`Zeta`] distribution //! - [`Zipf`] distribution //! - Gamma and derived distributions: @@ -100,6 +101,7 @@ pub use self::cauchy::{Cauchy, Error as CauchyError}; #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use self::dirichlet::{Dirichlet, Error as DirichletError}; pub use self::exponential::{Error as ExpError, Exp, Exp1}; +pub use self::frechet::{Error as FrechetError, Frechet}; pub use self::gamma::{ Beta, BetaError, ChiSquared, ChiSquaredError, Error as GammaError, FisherF, FisherFError, Gamma, StudentT, @@ -186,6 +188,7 @@ mod binomial; mod cauchy; mod dirichlet; mod exponential; +mod frechet; mod gamma; mod geometric; mod gumbel; From d63324a596cf47db2966c89d0c891ebec72fc887 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Tue, 31 Aug 2021 19:37:17 -0500 Subject: [PATCH 176/443] Fix backticks --- rand_distr/src/frechet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index 3f79f2119a6..0239fe83b12 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -16,7 +16,7 @@ use rand::Rng; /// Samples floating-point numbers according to the Fréchet distribution /// /// This distribution has density function: -/// `f(x) = [(x - μ) / σ]^(-1 - α)` exp[-(x - μ) / σ]^(-α) α / σ , +/// `f(x) = [(x - μ) / σ]^(-1 - α) exp[-(x - μ) / σ]^(-α) α / σ`, /// where `μ` is the location parameter, `σ` the scale parameter, and `α` the shape parameter. /// /// # Example From 1544c728cd7725797e2e197aefd817603b0f227e Mon Sep 17 00:00:00 2001 From: "Brandon H. Gomes" Date: Sat, 4 Sep 2021 18:33:39 -0400 Subject: [PATCH 177/443] fix no_std/alloc/std compatibility issues - using min_const_gen on no_std fails to compile because of a bad import - sample_efraimidis_spirakis only requires alloc but it was marked with a std requirement --- benches/distributions.rs | 8 ++++---- benches/generators.rs | 2 +- benches/misc.rs | 4 ++-- benches/seq.rs | 4 ++-- rand_distr/benches/src/distributions.rs | 2 +- rand_distr/tests/uniformity.rs | 2 +- src/distributions/distribution.rs | 4 ++-- src/distributions/other.rs | 2 +- src/distributions/uniform.rs | 9 ++++----- src/seq/index.rs | 10 +++++----- 10 files changed, 23 insertions(+), 24 deletions(-) diff --git a/benches/distributions.rs b/benches/distributions.rs index 7d8ac94c37b..76d5d258d9d 100644 --- a/benches/distributions.rs +++ b/benches/distributions.rs @@ -18,9 +18,9 @@ const RAND_BENCH_N: u64 = 1000; use rand::distributions::{Alphanumeric, Open01, OpenClosed01, Standard, Uniform}; use rand::distributions::uniform::{UniformInt, UniformSampler}; -use std::mem::size_of; -use std::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8}; -use std::time::Duration; +use core::mem::size_of; +use core::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8}; +use core::time::Duration; use test::{Bencher, black_box}; use rand::prelude::*; @@ -199,7 +199,7 @@ macro_rules! gen_range_int { for _ in 0..RAND_BENCH_N { accum = accum.wrapping_add(rng.gen_range($low..high)); // force recalculation of range each time - high = high.wrapping_add(1) & std::$ty::MAX; + high = high.wrapping_add(1) & core::$ty::MAX; } accum }); diff --git a/benches/generators.rs b/benches/generators.rs index f59c22224fb..65305a18e1f 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -14,7 +14,7 @@ extern crate test; const RAND_BENCH_N: u64 = 1000; const BYTES_LEN: usize = 1024; -use std::mem::size_of; +use core::mem::size_of; use test::{black_box, Bencher}; use rand::prelude::*; diff --git a/benches/misc.rs b/benches/misc.rs index 11d12eb24ad..f0b761f99ed 100644 --- a/benches/misc.rs +++ b/benches/misc.rs @@ -98,7 +98,7 @@ fn misc_bernoulli_var(b: &mut Bencher) { #[bench] fn gen_1kb_u16_iter_repeat(b: &mut Bencher) { - use std::iter; + use core::iter; let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { let v: Vec = iter::repeat(()).map(|()| rng.gen()).take(512).collect(); @@ -141,7 +141,7 @@ fn gen_1kb_u16_fill(b: &mut Bencher) { #[bench] fn gen_1kb_u64_iter_repeat(b: &mut Bencher) { - use std::iter; + use core::iter; let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { let v: Vec = iter::repeat(()).map(|()| rng.gen()).take(128).collect(); diff --git a/benches/seq.rs b/benches/seq.rs index a9bd88ff882..5b3a846f60b 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -15,7 +15,7 @@ use test::Bencher; use rand::prelude::*; use rand::seq::*; -use std::mem::size_of; +use core::mem::size_of; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. @@ -116,7 +116,7 @@ impl Iterator for WindowHintedIterator< } fn size_hint(&self) -> (usize, Option) { - (std::cmp::min(self.iter.len(), self.window_size), None) + (core::cmp::min(self.iter.len(), self.window_size), None) } } diff --git a/rand_distr/benches/src/distributions.rs b/rand_distr/benches/src/distributions.rs index 61c2aa1883c..d638debd526 100644 --- a/rand_distr/benches/src/distributions.rs +++ b/rand_distr/benches/src/distributions.rs @@ -17,7 +17,7 @@ use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use criterion_cycles_per_byte::CyclesPerByte; -use std::mem::size_of; +use core::mem::size_of; use rand::prelude::*; use rand_distr::*; diff --git a/rand_distr/tests/uniformity.rs b/rand_distr/tests/uniformity.rs index 4a64babdc76..d37ef0a9d06 100644 --- a/rand_distr/tests/uniformity.rs +++ b/rand_distr/tests/uniformity.rs @@ -48,7 +48,7 @@ fn unit_sphere() { #[test] fn unit_circle() { - use std::f64::consts::PI; + use core::f64::consts::PI; let mut h = Histogram100::with_const_width(-PI, PI); let dist = rand_distr::UnitCircle; let mut rng = rand_pcg::Pcg32::from_entropy(); diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index a516a906bda..c5cf6a607b4 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -209,7 +209,7 @@ pub trait DistString { #[cfg(test)] mod tests { - use crate::distributions::{Alphanumeric, Distribution, Standard, Uniform}; + use crate::distributions::{Distribution, Uniform}; use crate::Rng; #[test] @@ -258,7 +258,7 @@ mod tests { #[cfg(feature = "alloc")] fn test_dist_string() { use core::str; - use crate::distributions::DistString; + use crate::distributions::{Alphanumeric, DistString, Standard}; let mut rng = crate::test::rng(213); let s1 = Alphanumeric.sample_string(&mut rng, 20); diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 4c2471e6273..6fb1307c38f 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -21,7 +21,7 @@ use crate::Rng; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; #[cfg(feature = "min_const_gen")] -use std::mem::{self, MaybeUninit}; +use core::mem::{self, MaybeUninit}; // ----- Sampling distributions ----- diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 11a791ef8d1..03d44023741 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -103,8 +103,7 @@ //! [`UniformDuration`]: crate::distributions::uniform::UniformDuration //! [`SampleBorrow::borrow`]: crate::distributions::uniform::SampleBorrow::borrow -#[cfg(not(feature = "std"))] use core::time::Duration; -#[cfg(feature = "std")] use std::time::Duration; +use core::time::Duration; use core::ops::{Range, RangeInclusive}; use crate::distributions::float::IntoFloat; @@ -1153,7 +1152,8 @@ mod tests { #[test] #[cfg(feature = "serde1")] fn test_serialization_uniform_duration() { - let distr = UniformDuration::new(std::time::Duration::from_secs(10), std::time::Duration::from_secs(60)); + use core::time::Duration; + let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)); let de_distr: UniformDuration = bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); assert_eq!( distr.offset, de_distr.offset @@ -1503,8 +1503,7 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_durations() { - #[cfg(not(feature = "std"))] use core::time::Duration; - #[cfg(feature = "std")] use std::time::Duration; + use core::time::Duration; let mut rng = crate::test::rng(253); diff --git a/src/seq/index.rs b/src/seq/index.rs index ae36c323708..de61b900d36 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -272,8 +272,8 @@ where R: Rng + ?Sized { /// `O(length + amount * log length)` time otherwise. /// /// Panics if `amount > length`. -#[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub fn sample_weighted( rng: &mut R, length: usize, weight: F, amount: usize, ) -> Result @@ -305,7 +305,7 @@ where /// + amount * log length)` time otherwise. /// /// Panics if `amount > length`. -#[cfg(feature = "std")] +#[cfg(feature = "alloc")] fn sample_efraimidis_spirakis( rng: &mut R, length: N, weight: F, amount: N, ) -> Result @@ -380,7 +380,7 @@ where #[cfg(not(feature = "nightly"))] { - use std::collections::BinaryHeap; + use alloc::collections::BinaryHeap; // Partially sort the array such that the `amount` elements with the largest // keys are first using a binary max heap. @@ -621,7 +621,7 @@ mod test { assert_eq!(v1, v2); } - #[cfg(feature = "std")] + #[cfg(feature = "alloc")] #[test] fn test_sample_weighted() { let seed_rng = crate::test::rng; From 251bda1eb4880e36881cf896714632411dcabffe Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 5 Sep 2021 19:47:08 +0200 Subject: [PATCH 178/443] Add test for `BlockRng` and `BlockRng64` --- rand_chacha/src/chacha.rs | 24 --------- rand_core/src/block.rs | 105 ++++++++++++++++++++++++++++++++++++++ rand_core/src/lib.rs | 1 + rand_core/src/mock.rs | 53 +++++++++++++++++++ 4 files changed, 159 insertions(+), 24 deletions(-) create mode 100644 rand_core/src/mock.rs diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 5798b7ac3bf..50da81bfafe 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -629,28 +629,4 @@ mod test { rng.set_word_pos(0); assert_eq!(rng.get_word_pos(), 0); } - - #[test] - fn test_chacha_next_u32_vs_next_u64() { - let mut rng1 = ChaChaRng::from_seed(Default::default()); - let mut rng2 = rng1.clone(); - let mut rng3 = rng1.clone(); - - let mut a = [0; 16]; - (&mut a[..4]).copy_from_slice(&rng1.next_u32().to_le_bytes()); - (&mut a[4..12]).copy_from_slice(&rng1.next_u64().to_le_bytes()); - (&mut a[12..]).copy_from_slice(&rng1.next_u32().to_le_bytes()); - - let mut b = [0; 16]; - (&mut b[..4]).copy_from_slice(&rng2.next_u32().to_le_bytes()); - (&mut b[4..8]).copy_from_slice(&rng2.next_u32().to_le_bytes()); - (&mut b[8..]).copy_from_slice(&rng2.next_u64().to_le_bytes()); - assert_eq!(a, b); - - let mut c = [0; 16]; - (&mut c[..8]).copy_from_slice(&rng3.next_u64().to_le_bytes()); - (&mut c[8..12]).copy_from_slice(&rng3.next_u32().to_le_bytes()); - (&mut c[12..]).copy_from_slice(&rng3.next_u32().to_le_bytes()); - assert_eq!(a, c); - } } diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 666c45072bb..e78f285d7d0 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -432,3 +432,108 @@ impl SeedableRng for BlockRng64 { } impl CryptoRng for BlockRng {} + +#[cfg(test)] +mod test { + use crate::{SeedableRng, RngCore}; + use crate::block::{BlockRng, BlockRng64, BlockRngCore}; + use crate::mock::StepRng; + + #[derive(Debug, Clone)] + struct DummyRng { + rng: StepRng, + } + + impl BlockRngCore for DummyRng { + type Item = u32; + + type Results = [u32; 16]; + + fn generate(&mut self, results: &mut Self::Results) { + for r in results { + *r = self.rng.next_u32(); + } + } + } + + impl SeedableRng for DummyRng { + type Seed = [u8; 8]; + + fn from_seed(seed: Self::Seed) -> Self { + DummyRng { rng: StepRng::new(u64::from_le_bytes(seed), 1) } + } + } + + #[test] + fn blockrng_next_u32_vs_next_u64() { + let mut rng1 = BlockRng::::from_seed(Default::default()); + let mut rng2 = rng1.clone(); + let mut rng3 = rng1.clone(); + + let mut a = [0; 16]; + (&mut a[..4]).copy_from_slice(&rng1.next_u32().to_le_bytes()); + (&mut a[4..12]).copy_from_slice(&rng1.next_u64().to_le_bytes()); + (&mut a[12..]).copy_from_slice(&rng1.next_u32().to_le_bytes()); + + let mut b = [0; 16]; + (&mut b[..4]).copy_from_slice(&rng2.next_u32().to_le_bytes()); + (&mut b[4..8]).copy_from_slice(&rng2.next_u32().to_le_bytes()); + (&mut b[8..]).copy_from_slice(&rng2.next_u64().to_le_bytes()); + assert_eq!(a, b); + + let mut c = [0; 16]; + (&mut c[..8]).copy_from_slice(&rng3.next_u64().to_le_bytes()); + (&mut c[8..12]).copy_from_slice(&rng3.next_u32().to_le_bytes()); + (&mut c[12..]).copy_from_slice(&rng3.next_u32().to_le_bytes()); + assert_eq!(a, c); + } + + #[derive(Debug, Clone)] + struct DummyRng64 { + rng: StepRng, + } + + impl BlockRngCore for DummyRng64 { + type Item = u64; + + type Results = [u64; 8]; + + fn generate(&mut self, results: &mut Self::Results) { + for r in results { + *r = self.rng.next_u64(); + } + } + } + + impl SeedableRng for DummyRng64 { + type Seed = [u8; 8]; + + fn from_seed(seed: Self::Seed) -> Self { + DummyRng64 { rng: StepRng::new(u64::from_le_bytes(seed), 1) } + } + } + + #[test] + fn blockrng64_next_u32_vs_next_u64() { + let mut rng1 = BlockRng64::::from_seed(Default::default()); + let mut rng2 = rng1.clone(); + let mut rng3 = rng1.clone(); + + let mut a = [0; 16]; + (&mut a[..4]).copy_from_slice(&rng1.next_u32().to_le_bytes()); + (&mut a[4..12]).copy_from_slice(&rng1.next_u64().to_le_bytes()); + (&mut a[12..]).copy_from_slice(&rng1.next_u32().to_le_bytes()); + + let mut b = [0; 16]; + (&mut b[..4]).copy_from_slice(&rng2.next_u32().to_le_bytes()); + (&mut b[4..8]).copy_from_slice(&rng2.next_u32().to_le_bytes()); + (&mut b[8..]).copy_from_slice(&rng2.next_u64().to_le_bytes()); + assert_ne!(a, b); + + let mut c = [0; 16]; + (&mut c[..8]).copy_from_slice(&rng3.next_u64().to_le_bytes()); + (&mut c[8..12]).copy_from_slice(&rng3.next_u32().to_le_bytes()); + (&mut c[12..]).copy_from_slice(&rng3.next_u32().to_le_bytes()); + assert_eq!(b, c); + } +} diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index bc24270771b..e4213f6bddd 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -54,6 +54,7 @@ mod error; pub mod impls; pub mod le; #[cfg(feature = "getrandom")] mod os; +#[cfg(test)] mod mock; /// The core of a random number generator. diff --git a/rand_core/src/mock.rs b/rand_core/src/mock.rs new file mode 100644 index 00000000000..26a1702c8a4 --- /dev/null +++ b/rand_core/src/mock.rs @@ -0,0 +1,53 @@ +use crate::{impls, Error, RngCore}; + +/// A simple implementation of `RngCore` for testing purposes. +/// +/// This generates an arithmetic sequence (i.e. adds a constant each step) +/// over a `u64` number, using wrapping arithmetic. If the increment is 0 +/// the generator yields a constant. +/// +/// ``` +/// use rand::Rng; +/// use rand::rngs::mock::StepRng; +/// +/// let mut my_rng = StepRng::new(2, 1); +/// let sample: [u64; 3] = my_rng.gen(); +/// assert_eq!(sample, [2, 3, 4]); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StepRng { + v: u64, + a: u64, +} + +impl StepRng { + /// Create a `StepRng`, yielding an arithmetic sequence starting with + /// `initial` and incremented by `increment` each time. + pub fn new(initial: u64, increment: u64) -> Self { + StepRng { + v: initial, + a: increment, + } + } +} + +impl RngCore for StepRng { + fn next_u32(&mut self) -> u32 { + self.next_u64() as u32 + } + + fn next_u64(&mut self) -> u64 { + let result = self.v; + self.v = self.v.wrapping_add(self.a); + result + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + impls::fill_bytes_via_next(self, dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.fill_bytes(dest); + Ok(()) + } +} From d8fd7ba6c06024cdb3c32e1870272ef4ef995809 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 5 Sep 2021 20:16:53 +0200 Subject: [PATCH 179/443] Fix `BlockRng::next_u32` --- rand_core/src/block.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index e78f285d7d0..19296e568d6 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -361,15 +361,17 @@ where self.half_used = false; } - self.half_used = !self.half_used; - self.index += self.half_used as usize; - #[cfg(target_endian = "little")] - let half_used = self.half_used; + let use_second_half = self.half_used; #[cfg(not(target_endian = "little"))] - let half_used = !self.half_used; + let use_second_half = !self.half_used; + + let shift = 32 * (use_second_half as usize); + + self.half_used = !self.half_used; + self.index += self.half_used as usize; - (self.results.as_ref()[index] >> (32 * (half_used as usize))) as u32 + (self.results.as_ref()[index] >> shift) as u32 } #[inline] From 0f0d66f94791d16ac06df113ebc3b51cd636db58 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 5 Sep 2021 20:18:44 +0200 Subject: [PATCH 180/443] Add license header --- rand_core/src/mock.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rand_core/src/mock.rs b/rand_core/src/mock.rs index 26a1702c8a4..7675f8c4bec 100644 --- a/rand_core/src/mock.rs +++ b/rand_core/src/mock.rs @@ -1,3 +1,11 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + use crate::{impls, Error, RngCore}; /// A simple implementation of `RngCore` for testing purposes. From 8eff02ab6cb906a8f71ff185e3ac6b60b17192c1 Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Sun, 5 Sep 2021 21:06:52 +0200 Subject: [PATCH 181/443] Implement skew normal distribution --- rand_distr/Cargo.toml | 2 + rand_distr/benches/src/distributions.rs | 7 + rand_distr/src/lib.rs | 11 +- rand_distr/src/skew_normal.rs | 218 ++++++++++++++++++++++++ rand_distr/tests/pdf.rs | 130 ++++++++++++-- 5 files changed, 348 insertions(+), 20 deletions(-) create mode 100644 rand_distr/src/skew_normal.rs diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 317168e9d14..d2afc9d9207 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -33,3 +33,5 @@ rand_pcg = { version = "0.3.0", path = "../rand_pcg" } rand = { path = "..", version = "0.8.0", default-features = false, features = ["std_rng", "std", "small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.13", features = [ "std" ] } +# Special functions for testing distributions +statrs = "0.15.0" diff --git a/rand_distr/benches/src/distributions.rs b/rand_distr/benches/src/distributions.rs index 61c2aa1883c..86b66a97440 100644 --- a/rand_distr/benches/src/distributions.rs +++ b/rand_distr/benches/src/distributions.rs @@ -141,6 +141,13 @@ fn bench(c: &mut Criterion) { }); } + { + let mut g = c.benchmark_group("skew_normal"); + distr_float!(g, "shape_zero", f64, SkewNormal::new(0.0, 1.0, 0.0).unwrap()); + distr_float!(g, "shape_positive", f64, SkewNormal::new(0.0, 1.0, 100.0).unwrap()); + distr_float!(g, "shape_negative", f64, SkewNormal::new(0.0, 1.0, -100.0).unwrap()); + } + { let mut g = c.benchmark_group("gamma"); distr_float!(g, "gamma_large_shape", f64, Gamma::new(10., 1.0).unwrap()); diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 46860e31d06..b1b73555980 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -43,6 +43,7 @@ //! - Related to real-valued quantities that grow linearly //! (e.g. errors, offsets): //! - [`Normal`] distribution, and [`StandardNormal`] as a primitive +//! - [`SkewNormal`] distribution //! - [`Cauchy`] distribution //! - Related to Bernoulli trials (yes/no events, with a given probability): //! - [`Binomial`] distribution @@ -107,19 +108,22 @@ pub use self::gamma::{ pub use self::geometric::{Error as GeoError, Geometric, StandardGeometric}; pub use self::gumbel::{Error as GumbelError, Gumbel}; pub use self::hypergeometric::{Error as HyperGeoError, Hypergeometric}; -pub use self::inverse_gaussian::{InverseGaussian, Error as InverseGaussianError}; +pub use self::inverse_gaussian::{Error as InverseGaussianError, InverseGaussian}; pub use self::normal::{Error as NormalError, LogNormal, Normal, StandardNormal}; -pub use self::normal_inverse_gaussian::{NormalInverseGaussian, Error as NormalInverseGaussianError}; +pub use self::normal_inverse_gaussian::{ + Error as NormalInverseGaussianError, NormalInverseGaussian, +}; pub use self::pareto::{Error as ParetoError, Pareto}; pub use self::pert::{Pert, PertError}; pub use self::poisson::{Error as PoissonError, Poisson}; +pub use self::skew_normal::{Error as SkewNormalError, SkewNormal}; pub use self::triangular::{Triangular, TriangularError}; pub use self::unit_ball::UnitBall; pub use self::unit_circle::UnitCircle; pub use self::unit_disc::UnitDisc; pub use self::unit_sphere::UnitSphere; pub use self::weibull::{Error as WeibullError, Weibull}; -pub use self::zipf::{ZetaError, Zeta, ZipfError, Zipf}; +pub use self::zipf::{Zeta, ZetaError, Zipf, ZipfError}; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use rand::distributions::{WeightedError, WeightedIndex}; @@ -196,6 +200,7 @@ mod normal_inverse_gaussian; mod pareto; mod pert; mod poisson; +mod skew_normal; mod triangular; mod unit_ball; mod unit_circle; diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs new file mode 100644 index 00000000000..de4f5ddc722 --- /dev/null +++ b/rand_distr/src/skew_normal.rs @@ -0,0 +1,218 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Skew Normal distribution. + +use crate::{Distribution, StandardNormal}; +use core::fmt; +use num_traits::Float; +use rand::Rng; + +/// The [skew normal distribution] `SN(location, scale, shape)`. +/// +/// The skew normal distribution is a generalization of the +/// [`Normal`] distribution to allow for non-zero skewness. +/// +/// It has the density function +/// `f(x) = 2 / scale * phi((x - location) / scale) * Phi(alpha * (x - location) / scale)` +/// where `phi` and `Phi` are the density and distribution of a standard normal variable. +/// +/// # Example +/// +/// ``` +/// use rand_distr::{SkewNormal, Distribution}; +/// +/// // location 2, scale 3, shape 1 +/// let skew_normal = SkewNormal::new(2.0, 3.0, 1.0).unwrap(); +/// let v = skew_normal.sample(&mut rand::thread_rng()); +/// println!("{} is from a SN(2, 3, 1) distribution", v) +/// ``` +/// +/// # Implementation details +/// +/// We are using the algorithm from [A Method to Simulate the Skew Normal Distribution]. +/// +/// [`skew normal distribution`]: https://en.wikipedia.org/wiki/Skew_normal_distribution +/// [A Method to Simulate the Skew Normal Distribution]: +/// Ghorbanzadeh, D. , Jaupi, L. and Durand, P. (2014) +/// [A Method to Simulate the Skew Normal Distribution](https://dx.doi.org/10.4236/am.2014.513201). +/// Applied Mathematics, 5, 2073-2076. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct SkewNormal +where + F: Float, + StandardNormal: Distribution, +{ + location: F, + scale: F, + shape: F, +} + +/// Error type returned from `SkewNormal::new`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// The scale parameter is not finite or not positive. + BadScale, + /// The shape parameter is not finite. + BadShape, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::BadScale => "scale parameter is non-finite in skew normal distribution", + Error::BadShape => "shape parameter is non-finite in skew normal distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + +impl SkewNormal +where + F: Float, + StandardNormal: Distribution, +{ + /// Construct, from location, scale and shape. + /// + /// Parameters: + /// + /// - location (unrestricted) + /// - scale (must be finite and positive) + /// - shape (must be finite) + #[inline] + pub fn new(location: F, scale: F, shape: F) -> Result, Error> { + if !scale.is_finite() || !(scale > F::zero()) { + return Err(Error::BadScale); + } + if !shape.is_finite() { + return Err(Error::BadShape); + } + Ok(SkewNormal { + location, + scale, + shape, + }) + } + + /// Returns the location of the distribution. + pub fn location(&self) -> F { + self.location + } + + /// Returns the scale of the distribution. + pub fn scale(&self) -> F { + self.scale + } + + /// Returns the shape of the distribution. + pub fn shape(&self) -> F { + self.shape + } +} + +impl Distribution for SkewNormal +where + F: Float, + StandardNormal: Distribution, +{ + fn sample(&self, rng: &mut R) -> F { + let linear_map = |x: F| -> F { x * self.scale + self.location }; + let u_1: F = rng.sample(StandardNormal); + if self.shape == F::zero() { + linear_map(u_1) + } else { + let u_2 = rng.sample(StandardNormal); + let (u, v) = (u_1.max(u_2), u_1.min(u_2)); + if self.shape == -F::one() { + linear_map(v) + } else if self.shape == F::one() { + linear_map(u) + } else { + let normalized = ((F::one() + self.shape) * u + (F::one() - self.shape) * v) + / ((F::one() + self.shape * self.shape).sqrt() + * F::from(std::f64::consts::SQRT_2).unwrap()); + linear_map(normalized) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_samples>( + distr: D, zero: F, expected: &[F], + ) { + let mut rng = crate::test::rng(213); + let mut buf = [zero; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + #[test] + #[should_panic] + fn invalid_scale_nan() { + SkewNormal::new(0.0, core::f64::NAN, 0.0).unwrap(); + } + + #[test] + #[should_panic] + fn invalid_scale_zero() { + SkewNormal::new(0.0, 0.0, 0.0).unwrap(); + } + + #[test] + #[should_panic] + fn invalid_scale_negative() { + SkewNormal::new(0.0, -1.0, 0.0).unwrap(); + } + + #[test] + #[should_panic] + fn invalid_scale_infinite() { + SkewNormal::new(0.0, core::f64::INFINITY, 0.0).unwrap(); + } + + #[test] + #[should_panic] + fn invalid_shape_nan() { + SkewNormal::new(0.0, 1.0, core::f64::NAN).unwrap(); + } + + #[test] + #[should_panic] + fn invalid_shape_infinite() { + SkewNormal::new(0.0, 1.0, core::f64::INFINITY).unwrap(); + } + + #[test] + fn skew_normal_value_stability() { + test_samples( + SkewNormal::new(0.0, 1.0, 0.0).unwrap(), + 0f32, + &[-0.11844189, 0.781378, 0.06563994, -1.1932899], + ); + test_samples( + SkewNormal::new(0.0, 1.0, 0.0).unwrap(), + 0f64, + &[ + -0.11844188827977231, + 0.7813779637772346, + 0.06563993969580051, + -1.1932899004186373, + ], + ); + } +} diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index b1d1aa28174..21285f10d53 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -10,7 +10,7 @@ use average::Histogram; use rand::{Rng, SeedableRng}; -use rand_distr::Normal; +use rand_distr::{Normal, SkewNormal}; const HIST_LEN: usize = 100; average::define_histogram!(hist, crate::HIST_LEN); @@ -27,19 +27,21 @@ fn normal() { const MAX_X: f64 = 5.; let dist = Normal::new(MEAN, STD_DEV).unwrap(); - let mut hist = Histogram100::with_const_width(MIN_X,MAX_X); + let mut hist = Histogram100::with_const_width(MIN_X, MAX_X); let mut rng = rand::rngs::SmallRng::seed_from_u64(1); for _ in 0..N_SAMPLES { - let _ = hist.add(rng.sample(dist)); // Ignore out-of-range values + let _ = hist.add(rng.sample(dist)); // Ignore out-of-range values } - println!("Sampled normal distribution:\n{}", - sparkline::render_u64_as_string(hist.bins())); + println!( + "Sampled normal distribution:\n{}", + sparkline::render_u64_as_string(hist.bins()) + ); fn pdf(x: f64) -> f64 { - (-0.5 * ((x - MEAN) / STD_DEV).powi(2)).exp() / - (STD_DEV * (2. * core::f64::consts::PI).sqrt()) + (-0.5 * ((x - MEAN) / STD_DEV).powi(2)).exp() + / (STD_DEV * (2. * core::f64::consts::PI).sqrt()) } let mut bin_centers = hist.centers(); @@ -48,19 +50,25 @@ fn normal() { *e = pdf(bin_centers.next().unwrap()); } - println!("Expected normal distribution:\n{}", - sparkline::render_u64_as_string(hist.bins())); + println!( + "Expected normal distribution:\n{}", + sparkline::render_u64_as_string(hist.bins()) + ); let mut diff = [0.; HIST_LEN]; for (i, n) in hist.normalized_bins().enumerate() { - let bin = (n as f64) / (N_SAMPLES as f64) ; + let bin = (n as f64) / (N_SAMPLES as f64); diff[i] = (bin - expected[i]).abs(); } - println!("Difference:\n{}", - sparkline::render_f64_as_string(&diff[..])); - println!("max diff: {:?}", diff.iter().fold( - core::f64::NEG_INFINITY, |a, &b| a.max(b))); + println!( + "Difference:\n{}", + sparkline::render_f64_as_string(&diff[..]) + ); + println!( + "max diff: {:?}", + diff.iter().fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) + ); // Check that the differences are significantly smaller than the expected error. let mut expected_error = [0.; HIST_LEN]; @@ -74,8 +82,12 @@ fn normal() { } // TODO: Calculate error from distribution cutoff / normalization - println!("max expected_error: {:?}", expected_error.iter().fold( - core::f64::NEG_INFINITY, |a, &b| a.max(b))); + println!( + "max expected_error: {:?}", + expected_error + .iter() + .fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) + ); for (&d, &e) in diff.iter().zip(expected_error.iter()) { // Difference larger than 3 standard deviations or cutoff let tol = (3. * e).max(1e-4); @@ -83,4 +95,88 @@ fn normal() { panic!("Difference = {} * tol", d / tol); } } -} \ No newline at end of file +} + +#[test] +fn skew_normal() { + const N_SAMPLES: u64 = 1_000_000; + const LOCATION: f64 = 2.; + const SCALE: f64 = 0.5; + const SHAPE: f64 = -3.0; + const MIN_X: f64 = -1.; + const MAX_X: f64 = 4.; + + let dist = SkewNormal::new(LOCATION, SCALE, SHAPE).unwrap(); + let mut hist = Histogram100::with_const_width(MIN_X, MAX_X); + let mut rng = rand::rngs::SmallRng::seed_from_u64(1); + + for _ in 0..N_SAMPLES { + let _ = hist.add(rng.sample(dist)); // Ignore out-of-range values + } + + println!( + "Sampled skew normal distribution:\n{}", + sparkline::render_u64_as_string(hist.bins()) + ); + + fn pdf(x: f64) -> f64 { + let x_normalized = (x - LOCATION) / SCALE; + let normal_density_x = + (-0.5 * (x_normalized).powi(2)).exp() / (2. * core::f64::consts::PI).sqrt(); + let normal_distribution_x = 0.5 + * (1.0 + statrs::function::erf::erf(SHAPE * x_normalized / core::f64::consts::SQRT_2)); + 2.0 / SCALE * normal_density_x * normal_distribution_x + } + + let mut bin_centers = hist.centers(); + let mut expected = [0.; HIST_LEN]; + for e in &mut expected[..] { + *e = pdf(bin_centers.next().unwrap()); + } + + println!( + "Expected skew normal distribution:\n{}", + sparkline::render_u64_as_string(hist.bins()) + ); + + let mut diff = [0.; HIST_LEN]; + for (i, n) in hist.normalized_bins().enumerate() { + let bin = (n as f64) / (N_SAMPLES as f64); + diff[i] = (bin - expected[i]).abs(); + } + + println!( + "Difference:\n{}", + sparkline::render_f64_as_string(&diff[..]) + ); + println!( + "max diff: {:?}", + diff.iter().fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) + ); + + // Check that the differences are significantly smaller than the expected error. + let mut expected_error = [0.; HIST_LEN]; + // Calculate error from histogram + for (err, var) in expected_error.iter_mut().zip(hist.variances()) { + *err = var.sqrt() / (N_SAMPLES as f64); + } + // Normalize error by bin width + for (err, width) in expected_error.iter_mut().zip(hist.widths()) { + *err /= width; + } + // TODO: Calculate error from distribution cutoff / normalization + + println!( + "max expected_error: {:?}", + expected_error + .iter() + .fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) + ); + for (&d, &e) in diff.iter().zip(expected_error.iter()) { + // Difference larger than 3 standard deviations or cutoff + let tol = (3. * e).max(1e-4); + if d > tol { + panic!("Difference = {} * tol", d / tol); + } + } +} From 36fd688fb2a0e22c1b50b751395dbc277a7e5e0c Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Sun, 5 Sep 2021 21:12:14 +0200 Subject: [PATCH 182/443] Update Changelog --- rand_distr/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 04d50238274..9b919f398f2 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - New `Zeta` and `Zipf` distributions (#1136) +- New `SkewNormal` distribution (#1149) ## [0.4.1] - 2021-06-15 - Empirically test PDF of normal distribution (#1121) From 7577c39da86d311db018ed34e1dc7b9d15502956 Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Mon, 6 Sep 2021 11:06:33 +0200 Subject: [PATCH 183/443] Correcting use of std crate by falling back to core --- rand_distr/src/skew_normal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index de4f5ddc722..7b2eaf0f1c0 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -139,7 +139,7 @@ where } else { let normalized = ((F::one() + self.shape) * u + (F::one() - self.shape) * v) / ((F::one() + self.shape * self.shape).sqrt() - * F::from(std::f64::consts::SQRT_2).unwrap()); + * F::from(core::f64::consts::SQRT_2).unwrap()); linear_map(normalized) } } From f5cfbc4b19400e548b1af2b5d81919402d3d7fd0 Mon Sep 17 00:00:00 2001 From: "Brandon H. Gomes" Date: Mon, 6 Sep 2021 12:19:39 -0400 Subject: [PATCH 184/443] revert sample_efraimidis_spirakis and fix suggestions --- .github/workflows/test.yml | 6 +++--- src/distributions/other.rs | 2 +- src/distributions/uniform.rs | 3 --- src/seq/index.rs | 14 +++++++------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eaee94097d7..caaea15d88b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: env: RUSTDOCFLAGS: --cfg doc_cfg # --all builds all crates, but with default features for other crates (okay in this case) - run: cargo deadlinks --ignore-fragments -- --all --features nightly,serde1,getrandom,small_rng + run: cargo deadlinks --ignore-fragments -- --all --features nightly,serde1,getrandom,small_rng,min_const_gen test: runs-on: ${{ matrix.os }} @@ -80,8 +80,8 @@ jobs: - name: Test rand run: | cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng - cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng + cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng,min_const_gen + cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng,min_const_gen # all stable features: cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng cargo test --target ${{ matrix.target }} --examples diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 6fb1307c38f..03802a76d5f 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -189,8 +189,8 @@ tuple_impl! {A, B, C, D, E, F, G, H, I, J} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K, L} -#[cfg_attr(doc_cfg, doc(cfg(feature = "min_const_gen")))] #[cfg(feature = "min_const_gen")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "min_const_gen")))] impl Distribution<[T; N]> for Standard where Standard: Distribution { diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 03d44023741..096009f9ecb 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1152,7 +1152,6 @@ mod tests { #[test] #[cfg(feature = "serde1")] fn test_serialization_uniform_duration() { - use core::time::Duration; let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)); let de_distr: UniformDuration = bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); assert_eq!( @@ -1503,8 +1502,6 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_durations() { - use core::time::Duration; - let mut rng = crate::test::rng(253); let v = &[ diff --git a/src/seq/index.rs b/src/seq/index.rs index de61b900d36..b38e4649d1f 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -16,11 +16,11 @@ use alloc::collections::BTreeSet; #[cfg(feature = "std")] use std::collections::HashSet; -#[cfg(feature = "alloc")] -use crate::distributions::{uniform::SampleUniform, Distribution, Uniform}; #[cfg(feature = "std")] use crate::distributions::WeightedError; -use crate::Rng; + +#[cfg(feature = "alloc")] +use crate::{Rng, distributions::{uniform::SampleUniform, Distribution, Uniform}}; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; @@ -272,8 +272,8 @@ where R: Rng + ?Sized { /// `O(length + amount * log length)` time otherwise. /// /// Panics if `amount > length`. -#[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn sample_weighted( rng: &mut R, length: usize, weight: F, amount: usize, ) -> Result @@ -305,7 +305,7 @@ where /// + amount * log length)` time otherwise. /// /// Panics if `amount > length`. -#[cfg(feature = "alloc")] +#[cfg(feature = "std")] fn sample_efraimidis_spirakis( rng: &mut R, length: N, weight: F, amount: N, ) -> Result @@ -621,7 +621,7 @@ mod test { assert_eq!(v1, v2); } - #[cfg(feature = "alloc")] + #[cfg(feature = "std")] #[test] fn test_sample_weighted() { let seed_rng = crate::test::rng; From 6d4baefac7910f5d87685138c80f177485a2667c Mon Sep 17 00:00:00 2001 From: "Brandon H. Gomes" Date: Mon, 6 Sep 2021 12:49:40 -0400 Subject: [PATCH 185/443] move min_const_gen test to nightly workflow --- .github/workflows/test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index caaea15d88b..02b39b564ea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -77,11 +77,12 @@ jobs: cargo test --target ${{ matrix.target }} --all-features cargo test --target ${{ matrix.target }} --benches --features=nightly cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches + cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features min_const_gen - name: Test rand run: | cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng,min_const_gen - cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng,min_const_gen + cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng + cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng # all stable features: cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng cargo test --target ${{ matrix.target }} --examples From 0c5a5f9d41dd85e4310a07e1faf9e5a930854e7f Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 7 Sep 2021 18:25:18 +0200 Subject: [PATCH 186/443] Simplify dummy RNG for `BlockRng` test We no longer implement the non-block step RNG. --- rand_core/src/block.rs | 17 ++++++------ rand_core/src/lib.rs | 1 - rand_core/src/mock.rs | 61 ------------------------------------------ 3 files changed, 9 insertions(+), 70 deletions(-) delete mode 100644 rand_core/src/mock.rs diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 19296e568d6..304aa55ac40 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -439,11 +439,10 @@ impl CryptoRng for BlockRng {} mod test { use crate::{SeedableRng, RngCore}; use crate::block::{BlockRng, BlockRng64, BlockRngCore}; - use crate::mock::StepRng; #[derive(Debug, Clone)] struct DummyRng { - rng: StepRng, + counter: u32, } impl BlockRngCore for DummyRng { @@ -453,16 +452,17 @@ mod test { fn generate(&mut self, results: &mut Self::Results) { for r in results { - *r = self.rng.next_u32(); + *r = self.counter; + self.counter += 1; } } } impl SeedableRng for DummyRng { - type Seed = [u8; 8]; + type Seed = [u8; 4]; fn from_seed(seed: Self::Seed) -> Self { - DummyRng { rng: StepRng::new(u64::from_le_bytes(seed), 1) } + DummyRng { counter: u32::from_le_bytes(seed) } } } @@ -492,7 +492,7 @@ mod test { #[derive(Debug, Clone)] struct DummyRng64 { - rng: StepRng, + counter: u64, } impl BlockRngCore for DummyRng64 { @@ -502,7 +502,8 @@ mod test { fn generate(&mut self, results: &mut Self::Results) { for r in results { - *r = self.rng.next_u64(); + *r = self.counter; + self.counter += 1; } } } @@ -511,7 +512,7 @@ mod test { type Seed = [u8; 8]; fn from_seed(seed: Self::Seed) -> Self { - DummyRng64 { rng: StepRng::new(u64::from_le_bytes(seed), 1) } + DummyRng64 { counter: u64::from_le_bytes(seed) } } } diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index e4213f6bddd..bc24270771b 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -54,7 +54,6 @@ mod error; pub mod impls; pub mod le; #[cfg(feature = "getrandom")] mod os; -#[cfg(test)] mod mock; /// The core of a random number generator. diff --git a/rand_core/src/mock.rs b/rand_core/src/mock.rs deleted file mode 100644 index 7675f8c4bec..00000000000 --- a/rand_core/src/mock.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2021 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use crate::{impls, Error, RngCore}; - -/// A simple implementation of `RngCore` for testing purposes. -/// -/// This generates an arithmetic sequence (i.e. adds a constant each step) -/// over a `u64` number, using wrapping arithmetic. If the increment is 0 -/// the generator yields a constant. -/// -/// ``` -/// use rand::Rng; -/// use rand::rngs::mock::StepRng; -/// -/// let mut my_rng = StepRng::new(2, 1); -/// let sample: [u64; 3] = my_rng.gen(); -/// assert_eq!(sample, [2, 3, 4]); -/// ``` -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct StepRng { - v: u64, - a: u64, -} - -impl StepRng { - /// Create a `StepRng`, yielding an arithmetic sequence starting with - /// `initial` and incremented by `increment` each time. - pub fn new(initial: u64, increment: u64) -> Self { - StepRng { - v: initial, - a: increment, - } - } -} - -impl RngCore for StepRng { - fn next_u32(&mut self) -> u32 { - self.next_u64() as u32 - } - - fn next_u64(&mut self) -> u64 { - let result = self.v; - self.v = self.v.wrapping_add(self.a); - result - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - impls::fill_bytes_via_next(self, dest); - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) - } -} From 4e99eeed49600029f6fef5efad2ae435c0594c46 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 7 Sep 2021 20:55:36 +0200 Subject: [PATCH 187/443] Try to fix test on MIPS --- rand_core/src/block.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 304aa55ac40..45581abf684 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -361,12 +361,7 @@ where self.half_used = false; } - #[cfg(target_endian = "little")] - let use_second_half = self.half_used; - #[cfg(not(target_endian = "little"))] - let use_second_half = !self.half_used; - - let shift = 32 * (use_second_half as usize); + let shift = 32 * (self.half_used as usize); self.half_used = !self.half_used; self.index += self.half_used as usize; From 99da1ac2e28e4d203b952889ff125ad3d1761b39 Mon Sep 17 00:00:00 2001 From: Daniel Saxton Date: Tue, 7 Sep 2021 16:47:47 -0500 Subject: [PATCH 188/443] Add to changelog --- rand_distr/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 04d50238274..90b72cac24b 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - New `Zeta` and `Zipf` distributions (#1136) +- New `Gumbel` and `Frechet` distributions (#1168, #1171) ## [0.4.1] - 2021-06-15 - Empirically test PDF of normal distribution (#1121) From 3f331624006245f63497cdb4c134363453434993 Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Thu, 9 Sep 2021 20:36:56 +0200 Subject: [PATCH 189/443] Add documentation and test for NAN samples --- rand_distr/src/skew_normal.rs | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 7b2eaf0f1c0..99476f9416f 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -18,7 +18,7 @@ use rand::Rng; /// The skew normal distribution is a generalization of the /// [`Normal`] distribution to allow for non-zero skewness. /// -/// It has the density function +/// It has the density function, for `scale > 0`, /// `f(x) = 2 / scale * phi((x - location) / scale) * Phi(alpha * (x - location) / scale)` /// where `phi` and `Phi` are the density and distribution of a standard normal variable. /// @@ -37,7 +37,8 @@ use rand::Rng; /// /// We are using the algorithm from [A Method to Simulate the Skew Normal Distribution]. /// -/// [`skew normal distribution`]: https://en.wikipedia.org/wiki/Skew_normal_distribution +/// [skew normal distribution]: https://en.wikipedia.org/wiki/Skew_normal_distribution +/// [`Normal`]: struct.Normal.html /// [A Method to Simulate the Skew Normal Distribution]: /// Ghorbanzadeh, D. , Jaupi, L. and Durand, P. (2014) /// [A Method to Simulate the Skew Normal Distribution](https://dx.doi.org/10.4236/am.2014.513201). @@ -57,8 +58,8 @@ where /// Error type returned from `SkewNormal::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { - /// The scale parameter is not finite or not positive. - BadScale, + /// The scale parameter is not finite or it is less or equal to zero. + ScaleTooSmall, /// The shape parameter is not finite. BadShape, } @@ -66,7 +67,9 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - Error::BadScale => "scale parameter is non-finite in skew normal distribution", + Error::ScaleTooSmall => { + "scale parameter is either non-finite or it is less or equal to zero in skew normal distribution" + } Error::BadShape => "shape parameter is non-finite in skew normal distribution", }) } @@ -86,12 +89,12 @@ where /// Parameters: /// /// - location (unrestricted) - /// - scale (must be finite and positive) + /// - scale (must be finite and larger than zero) /// - shape (must be finite) #[inline] pub fn new(location: F, scale: F, shape: F) -> Result, Error> { if !scale.is_finite() || !(scale > F::zero()) { - return Err(Error::BadScale); + return Err(Error::ScaleTooSmall); } if !shape.is_finite() { return Err(Error::BadShape); @@ -197,6 +200,11 @@ mod tests { SkewNormal::new(0.0, 1.0, core::f64::INFINITY).unwrap(); } + #[test] + fn valid_location_nan() { + SkewNormal::new(core::f64::NAN, 1.0, 0.0).unwrap(); + } + #[test] fn skew_normal_value_stability() { test_samples( @@ -215,4 +223,17 @@ mod tests { ], ); } + + #[test] + fn skew_normal_value_location_nan() { + let skew_normal = SkewNormal::new(core::f64::NAN, 1.0, 0.0).unwrap(); + let mut rng = crate::test::rng(213); + let mut buf = [0.0; 4]; + for x in &mut buf { + *x = rng.sample(&skew_normal); + } + for value in buf { + assert!(value.is_nan()); + } + } } From dbf7e949a4b052fac5fa8389261ba6b30d75e64f Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Thu, 9 Sep 2021 21:03:19 +0200 Subject: [PATCH 190/443] Add test for infinite location in skew normal --- rand_distr/src/skew_normal.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 99476f9416f..63c286e3a99 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -222,6 +222,26 @@ mod tests { -1.1932899004186373, ], ); + test_samples( + SkewNormal::new(core::f64::INFINITY, 1.0, 0.0).unwrap(), + 0f64, + &[ + core::f64::INFINITY, + core::f64::INFINITY, + core::f64::INFINITY, + core::f64::INFINITY, + ], + ); + test_samples( + SkewNormal::new(core::f64::NEG_INFINITY, 1.0, 0.0).unwrap(), + 0f64, + &[ + core::f64::NEG_INFINITY, + core::f64::NEG_INFINITY, + core::f64::NEG_INFINITY, + core::f64::NEG_INFINITY, + ], + ); } #[test] From 36ee8236c4f7a57a2465fa5ba0648ec57860bfbf Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Thu, 9 Sep 2021 21:08:55 +0200 Subject: [PATCH 191/443] Change from statrs to special for usage of special functions in tests --- rand_distr/Cargo.toml | 2 +- rand_distr/tests/pdf.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index d2afc9d9207..680c86966a9 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -34,4 +34,4 @@ rand = { path = "..", version = "0.8.0", default-features = false, features = [" # Histogram implementation for testing uniformity average = { version = "0.13", features = [ "std" ] } # Special functions for testing distributions -statrs = "0.15.0" +special = "0.8.1" diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 21285f10d53..198b690d2ef 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -119,12 +119,13 @@ fn skew_normal() { sparkline::render_u64_as_string(hist.bins()) ); + use special::Error; fn pdf(x: f64) -> f64 { let x_normalized = (x - LOCATION) / SCALE; let normal_density_x = (-0.5 * (x_normalized).powi(2)).exp() / (2. * core::f64::consts::PI).sqrt(); - let normal_distribution_x = 0.5 - * (1.0 + statrs::function::erf::erf(SHAPE * x_normalized / core::f64::consts::SQRT_2)); + let normal_distribution_x = + 0.5 * (1.0 + (SHAPE * x_normalized / core::f64::consts::SQRT_2).error()); 2.0 / SCALE * normal_density_x * normal_distribution_x } From 6df504a590fb7a5e2e73811fa621db06cf80ad19 Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Thu, 9 Sep 2021 21:15:12 +0200 Subject: [PATCH 192/443] Correcting a test --- rand_distr/src/skew_normal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 63c286e3a99..efcdbcbc072 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -252,7 +252,7 @@ mod tests { for x in &mut buf { *x = rng.sample(&skew_normal); } - for value in buf { + for value in buf.iter() { assert!(value.is_nan()); } } From cda046507df689c26999e9c8e301abbe6ff7807f Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Fri, 10 Sep 2021 01:36:47 +0100 Subject: [PATCH 193/443] Add `min_const_gen` to "all stable features" test invocation `min_const_gen` is a stable feature, but was not added to this test invocation when it was introduced. Unblocks #1173. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eaee94097d7..c792c3a94ae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -83,7 +83,7 @@ jobs: cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng # all stable features: - cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng + cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng,min_const_gen cargo test --target ${{ matrix.target }} --examples - name: Test rand_core run: | From 85c9bc57bff1a5329cf45dddf295bc7da65bb41e Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 10 Sep 2021 15:23:11 +0200 Subject: [PATCH 194/443] Fix tests on nightly This basically requires fixing `packed_simd_2` by upgrading the minimal version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 19cf619a0fe..fe0b3ff6367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = fals [dependencies.packed_simd] # NOTE: so far no version works reliably due to dependence on unstable features package = "packed_simd_2" -version = "0.3.5" +version = "0.3.6" optional = true features = ["into_bits"] From 9cbcfe4725f18cde4c0f7849463dcce748847e5f Mon Sep 17 00:00:00 2001 From: Chris Connelly Date: Fri, 10 Sep 2021 17:30:13 +0100 Subject: [PATCH 195/443] Split all feature test invocation for MSRV and non-MSRV Since const generics are not stable in Rust 1.36.0, we cannot use the `min_const_gen` feature when testing the MSRV. --- .github/workflows/test.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c792c3a94ae..28ff439eda0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -82,9 +82,16 @@ jobs: cargo test --target ${{ matrix.target }} --lib --tests --no-default-features cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng - # all stable features: - cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng,min_const_gen cargo test --target ${{ matrix.target }} --examples + - name: Test rand (all stable features, non-MSRV) + if: ${{ matrix.toolchain != '1.36.0' }} + run: | + cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng,min_const_gen + - name: Test rand (all stable features, MSRV) + if: ${{ matrix.toolchain == '1.36.0' }} + run: | + # const generics are not stable on 1.36.0 + cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng - name: Test rand_core run: | cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml From 698ebe0535379cb58631dd6c3d4e649c342b7eb9 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 10 Sep 2021 21:48:39 +0200 Subject: [PATCH 196/443] Fix a clippy warning --- rand_distr/src/skew_normal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index efcdbcbc072..1a5a39c160a 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -40,9 +40,9 @@ use rand::Rng; /// [skew normal distribution]: https://en.wikipedia.org/wiki/Skew_normal_distribution /// [`Normal`]: struct.Normal.html /// [A Method to Simulate the Skew Normal Distribution]: -/// Ghorbanzadeh, D. , Jaupi, L. and Durand, P. (2014) -/// [A Method to Simulate the Skew Normal Distribution](https://dx.doi.org/10.4236/am.2014.513201). -/// Applied Mathematics, 5, 2073-2076. +/// Ghorbanzadeh, D. , Jaupi, L. and Durand, P. (2014) +/// [A Method to Simulate the Skew Normal Distribution](https://dx.doi.org/10.4236/am.2014.513201). +/// Applied Mathematics, 5, 2073-2076. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct SkewNormal From b7380fde25fe861c2178bba78009311db3f0f3b7 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 11 Sep 2021 00:01:03 +0200 Subject: [PATCH 197/443] rand: Update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e87add21bc..08ea063d23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,12 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [0.8.5] - unreleased ### Fixes -- Fix build on non-32/64-bit architectures (#1144) +- Fix build on non-32/64-bit architectures (#1144) +- Fix "min_const_gen" feature for `no_std` (#1173) + +### Rngs +- `StdRng`: Switch from HC128 to ChaCha12 on emscripten (#1142). + We now use ChaCha12 on all platforms. ## [0.8.4] - 2021-06-15 ### Additions From 40c49c2d1244afbe480619d82d2783e811805d99 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 11 Sep 2021 15:55:04 +0100 Subject: [PATCH 198/443] ReseedingRng: more robust fork handling --- src/rngs/adapter/reseeding.rs | 26 ++++++++++++++++++++------ src/rngs/thread.rs | 5 +++-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index 70b0b82307f..ae3fcbb2fc2 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -22,10 +22,10 @@ use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// /// - On a manual call to [`reseed()`]. /// - After `clone()`, the clone will be reseeded on first use. -/// - After a process is forked, the RNG in the child process is reseeded within -/// the next few generated values, depending on the block size of the -/// underlying PRNG. For ChaCha and Hc128 this is a maximum of -/// 15 `u32` values before reseeding. +/// - When a process is forked on UNIX, the RNGs in both the parent and child +/// processes will be reseeded just before the next call to +/// [`BlockRngCore::generate`], i.e. "soon". For ChaCha and Hc128 this is a +/// maximum of fifteen `u32` values before reseeding. /// - After the PRNG has generated a configurable number of random bytes. /// /// # When should reseeding after a fixed number of generated bytes be used? @@ -43,6 +43,12 @@ use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// Use [`ReseedingRng::new`] with a `threshold` of `0` to disable reseeding /// after a fixed number of generated bytes. /// +/// # Limitations +/// +/// It is recommended that a `ReseedingRng` (including `ThreadRng`) not be used +/// from a fork handler. +/// Use `OsRng` or `getrandom`, or defer your use of the RNG until later. +/// /// # Error handling /// /// Although unlikely, reseeding the wrapped PRNG can fail. `ReseedingRng` will @@ -310,8 +316,16 @@ mod fork { pub fn register_fork_handler() { static REGISTER: Once = Once::new(); - REGISTER.call_once(|| unsafe { - libc::pthread_atfork(None, None, Some(fork_handler)); + REGISTER.call_once(|| { + // Bump the counter before and after forking (see #1169): + let ret = unsafe { libc::pthread_atfork( + Some(fork_handler), + Some(fork_handler), + Some(fork_handler), + ) }; + if ret != 0 { + panic!("libc::pthread_atfork failed with code {}", ret); + } }); } } diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 13511e3ed2a..baebb1d99c7 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -40,8 +40,9 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// A reference to the thread-local generator /// /// An instance can be obtained via [`thread_rng`] or via `ThreadRng::default()`. -/// This handle is safe to use everywhere (including thread-local destructors) -/// but cannot be passed between threads (is not `Send` or `Sync`). +/// This handle is safe to use everywhere (including thread-local destructors), +/// though it is recommended not to use inside a fork handler. +/// The handle cannot be passed between threads (is not `Send` or `Sync`). /// /// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance /// and is automatically seeded from [`OsRng`]. From 0435f0fff3f47353c8ecedea6d5f9111ddd4b272 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 11 Sep 2021 16:55:53 +0100 Subject: [PATCH 199/443] Update changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e87add21bc..11bd4eefcf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [0.8.5] - unreleased ### Fixes -- Fix build on non-32/64-bit architectures (#1144) +- Fix build on non-32/64-bit architectures (#1144) +- Check `libc::pthread_atfork` return value with panic on error (#1178) +- More robust reseeding in case `ReseedingRng` is used from a fork handler (#1178) ## [0.8.4] - 2021-06-15 ### Additions From 7533de07748dbe957dcf74ba7c10ed0c82dcd597 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 11 Sep 2021 19:06:11 +0200 Subject: [PATCH 200/443] Fix reference in docs --- rand_distr/src/skew_normal.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 1a5a39c160a..7d91d0bdc46 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -39,10 +39,7 @@ use rand::Rng; /// /// [skew normal distribution]: https://en.wikipedia.org/wiki/Skew_normal_distribution /// [`Normal`]: struct.Normal.html -/// [A Method to Simulate the Skew Normal Distribution]: -/// Ghorbanzadeh, D. , Jaupi, L. and Durand, P. (2014) -/// [A Method to Simulate the Skew Normal Distribution](https://dx.doi.org/10.4236/am.2014.513201). -/// Applied Mathematics, 5, 2073-2076. +/// [A Method to Simulate the Skew Normal Distribution]: https://dx.doi.org/10.4236/am.2014.513201 #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct SkewNormal From 4addc7513be6c731e9c521e709dd4731324c1b3d Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 11 Sep 2021 19:20:23 +0200 Subject: [PATCH 201/443] Use slightly more random `DummyRng` --- rand_core/src/block.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 45581abf684..b3167f52cf1 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -448,7 +448,7 @@ mod test { fn generate(&mut self, results: &mut Self::Results) { for r in results { *r = self.counter; - self.counter += 1; + self.counter = self.counter.wrapping_add(3511615422); } } } @@ -463,7 +463,7 @@ mod test { #[test] fn blockrng_next_u32_vs_next_u64() { - let mut rng1 = BlockRng::::from_seed(Default::default()); + let mut rng1 = BlockRng::::from_seed([1, 2, 3, 4]); let mut rng2 = rng1.clone(); let mut rng3 = rng1.clone(); @@ -498,7 +498,7 @@ mod test { fn generate(&mut self, results: &mut Self::Results) { for r in results { *r = self.counter; - self.counter += 1; + self.counter = self.counter.wrapping_add(2781463553396133982); } } } @@ -513,7 +513,7 @@ mod test { #[test] fn blockrng64_next_u32_vs_next_u64() { - let mut rng1 = BlockRng64::::from_seed(Default::default()); + let mut rng1 = BlockRng64::::from_seed([1, 2, 3, 4, 5, 6, 7, 8]); let mut rng2 = rng1.clone(); let mut rng3 = rng1.clone(); From 9684ebf1bf80b9eb8aa2924da6b9748fb96d6750 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 11 Sep 2021 16:36:35 +0100 Subject: [PATCH 202/443] fill_via_chunks: use safe code via chunks_exact_mut on BE This (specifically, using chunks_exact_mut) actually improves performance substantially. --- rand_core/src/impls.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 2588a72ea3f..f16f5b5b8d2 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -58,9 +58,8 @@ macro_rules! fill_via_chunks { let chunk_size_u8 = min($src.len() * SIZE, $dst.len()); let chunk_size = (chunk_size_u8 + SIZE - 1) / SIZE; - // The following can be replaced with safe code, but unfortunately it's - // ca. 8% slower. if cfg!(target_endian = "little") { + // On LE we can do a simple copy, which is 25-50% faster: unsafe { core::ptr::copy_nonoverlapping( $src.as_ptr() as *const u8, @@ -68,15 +67,16 @@ macro_rules! fill_via_chunks { chunk_size_u8); } } else { - for (&n, chunk) in $src.iter().zip($dst.chunks_mut(SIZE)) { - let tmp = n.to_le(); - let src_ptr = &tmp as *const $ty as *const u8; - unsafe { - core::ptr::copy_nonoverlapping( - src_ptr, - chunk.as_mut_ptr(), - chunk.len()); - } + // This code is valid on all arches, but slower than the above: + let mut i = 0; + let mut iter = $dst[..chunk_size_u8].chunks_exact_mut(SIZE); + while let Some(chunk) = iter.next() { + chunk.copy_from_slice(&$src[i].to_le_bytes()); + i += 1; + } + let chunk = iter.into_remainder(); + if !chunk.is_empty() { + chunk.copy_from_slice(&$src[i].to_le_bytes()[..chunk.len()]); } } From eb324041b9e74dffc1af91332321776cd025b149 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sat, 11 Sep 2021 15:03:50 -0700 Subject: [PATCH 203/443] chacha: safer outputting --- rand_chacha/src/chacha.rs | 8 +------- rand_chacha/src/guts.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index c7150127b3c..fce8e493796 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -87,13 +87,7 @@ macro_rules! chacha_impl { type Results = Array64; #[inline] fn generate(&mut self, r: &mut Self::Results) { - // Fill slice of words by writing to equivalent slice of bytes, then fixing endianness. - self.state.refill4($rounds, unsafe { - &mut *(&mut *r as *mut Array64 as *mut [u8; 256]) - }); - for x in r.as_mut() { - *x = x.to_le(); - } + self.state.refill4($rounds, &mut r.0); } } diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index cee8cf75d4c..58eba9d61aa 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -14,7 +14,7 @@ use ppv_lite86::{dispatch, dispatch_light128}; pub use ppv_lite86::Machine; use ppv_lite86::{vec128_storage, ArithOps, BitOps32, LaneWords4, MultiLane, StoreBytes, Vec4}; -pub(crate) const BLOCK: usize = 64; +pub(crate) const BLOCK: usize = 16; pub(crate) const BLOCK64: u64 = BLOCK as u64; const LOG2_BUFBLOCKS: u64 = 2; const BUFBLOCKS: u64 = 1 << LOG2_BUFBLOCKS; @@ -81,7 +81,7 @@ impl ChaCha { /// Produce 4 blocks of output, advancing the state #[inline(always)] - pub fn refill4(&mut self, drounds: u32, out: &mut [u8; BUFSZ]) { + pub fn refill4(&mut self, drounds: u32, out: &mut [u32; BUFSZ]) { refill_wide(self, drounds, out) } @@ -114,7 +114,7 @@ impl ChaCha { #[allow(clippy::many_single_char_names)] #[inline(always)] fn refill_wide_impl( - m: Mach, state: &mut ChaCha, drounds: u32, out: &mut [u8; BUFSZ], + m: Mach, state: &mut ChaCha, drounds: u32, out: &mut [u32; BUFSZ], ) { let k = m.vec([0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]); let mut pos = state.pos64(m); @@ -159,17 +159,17 @@ fn refill_wide_impl( let sc = m.unpack(state.c); let sd = [m.unpack(state.d), d1, d2, d3]; state.d = d4.into(); - let mut words = out.chunks_exact_mut(16); + let mut words = out.chunks_exact_mut(4); for ((((&a, &b), &c), &d), &sd) in a.iter().zip(&b).zip(&c).zip(&d).zip(&sd) { - (a + k).write_le(words.next().unwrap()); - (b + sb).write_le(words.next().unwrap()); - (c + sc).write_le(words.next().unwrap()); - (d + sd).write_le(words.next().unwrap()); + words.next().unwrap().copy_from_slice(&(a + k).to_lanes()); + words.next().unwrap().copy_from_slice(&(b + sb).to_lanes()); + words.next().unwrap().copy_from_slice(&(c + sc).to_lanes()); + words.next().unwrap().copy_from_slice(&(d + sd).to_lanes()); } } dispatch!(m, Mach, { - fn refill_wide(state: &mut ChaCha, drounds: u32, out: &mut [u8; BUFSZ]) { + fn refill_wide(state: &mut ChaCha, drounds: u32, out: &mut [u32; BUFSZ]) { refill_wide_impl(m, state, drounds, out); } }); From 8006bc35e7c769daef269eb3e229c4761dd88d6a Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 12 Sep 2021 16:40:36 +0200 Subject: [PATCH 204/443] Use odd increments --- rand_core/src/block.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index b3167f52cf1..2c8576a2885 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -448,7 +448,7 @@ mod test { fn generate(&mut self, results: &mut Self::Results) { for r in results { *r = self.counter; - self.counter = self.counter.wrapping_add(3511615422); + self.counter = self.counter.wrapping_add(3511615421); } } } @@ -498,7 +498,7 @@ mod test { fn generate(&mut self, results: &mut Self::Results) { for r in results { *r = self.counter; - self.counter = self.counter.wrapping_add(2781463553396133982); + self.counter = self.counter.wrapping_add(2781463553396133981); } } } From 64ac013b2a9754dc114559958eaa72762ce6b582 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sun, 12 Sep 2021 16:43:14 +0200 Subject: [PATCH 205/443] Improve `BlockRng64` test --- rand_core/src/block.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 2c8576a2885..d311b68cfe6 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -527,6 +527,8 @@ mod test { (&mut b[4..8]).copy_from_slice(&rng2.next_u32().to_le_bytes()); (&mut b[8..]).copy_from_slice(&rng2.next_u64().to_le_bytes()); assert_ne!(a, b); + assert_eq!(&a[..4], &b[..4]); + assert_eq!(&a[4..12], &b[8..]); let mut c = [0; 16]; (&mut c[..8]).copy_from_slice(&rng3.next_u64().to_le_bytes()); From 7fa7c431706833b6d26c49b3ef8a3441035770f3 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sat, 11 Sep 2021 15:03:50 -0700 Subject: [PATCH 206/443] chacha: safer outputting: manually unroll the loop --- rand_chacha/src/guts.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index 58eba9d61aa..eeabd9f4c1d 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -159,13 +159,22 @@ fn refill_wide_impl( let sc = m.unpack(state.c); let sd = [m.unpack(state.d), d1, d2, d3]; state.d = d4.into(); - let mut words = out.chunks_exact_mut(4); - for ((((&a, &b), &c), &d), &sd) in a.iter().zip(&b).zip(&c).zip(&d).zip(&sd) { - words.next().unwrap().copy_from_slice(&(a + k).to_lanes()); - words.next().unwrap().copy_from_slice(&(b + sb).to_lanes()); - words.next().unwrap().copy_from_slice(&(c + sc).to_lanes()); - words.next().unwrap().copy_from_slice(&(d + sd).to_lanes()); - } + out[0..4].copy_from_slice(&(a[0] + k).to_lanes()); + out[4..8].copy_from_slice(&(b[0] + sb).to_lanes()); + out[8..12].copy_from_slice(&(c[0] + sc).to_lanes()); + out[12..16].copy_from_slice(&(d[0] + sd[0]).to_lanes()); + out[16..20].copy_from_slice(&(a[1] + k).to_lanes()); + out[20..24].copy_from_slice(&(b[1] + sb).to_lanes()); + out[24..28].copy_from_slice(&(c[1] + sc).to_lanes()); + out[28..32].copy_from_slice(&(d[1] + sd[1]).to_lanes()); + out[32..36].copy_from_slice(&(a[2] + k).to_lanes()); + out[36..40].copy_from_slice(&(b[2] + sb).to_lanes()); + out[40..44].copy_from_slice(&(c[2] + sc).to_lanes()); + out[44..48].copy_from_slice(&(d[2] + sd[2]).to_lanes()); + out[48..52].copy_from_slice(&(a[3] + k).to_lanes()); + out[52..56].copy_from_slice(&(b[3] + sb).to_lanes()); + out[56..60].copy_from_slice(&(c[3] + sc).to_lanes()); + out[60..64].copy_from_slice(&(d[3] + sd[3]).to_lanes()); } dispatch!(m, Mach, { From 7d9607a3a3ea6816f7105e508b79cfe664ecc4d4 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sun, 12 Sep 2021 12:53:11 -0700 Subject: [PATCH 207/443] chacha: safer outputting: 0..4 loop --- rand_chacha/src/guts.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index eeabd9f4c1d..0bda609ec69 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -159,22 +159,13 @@ fn refill_wide_impl( let sc = m.unpack(state.c); let sd = [m.unpack(state.d), d1, d2, d3]; state.d = d4.into(); - out[0..4].copy_from_slice(&(a[0] + k).to_lanes()); - out[4..8].copy_from_slice(&(b[0] + sb).to_lanes()); - out[8..12].copy_from_slice(&(c[0] + sc).to_lanes()); - out[12..16].copy_from_slice(&(d[0] + sd[0]).to_lanes()); - out[16..20].copy_from_slice(&(a[1] + k).to_lanes()); - out[20..24].copy_from_slice(&(b[1] + sb).to_lanes()); - out[24..28].copy_from_slice(&(c[1] + sc).to_lanes()); - out[28..32].copy_from_slice(&(d[1] + sd[1]).to_lanes()); - out[32..36].copy_from_slice(&(a[2] + k).to_lanes()); - out[36..40].copy_from_slice(&(b[2] + sb).to_lanes()); - out[40..44].copy_from_slice(&(c[2] + sc).to_lanes()); - out[44..48].copy_from_slice(&(d[2] + sd[2]).to_lanes()); - out[48..52].copy_from_slice(&(a[3] + k).to_lanes()); - out[52..56].copy_from_slice(&(b[3] + sb).to_lanes()); - out[56..60].copy_from_slice(&(c[3] + sc).to_lanes()); - out[60..64].copy_from_slice(&(d[3] + sd[3]).to_lanes()); + for i in 0..4 { + let i4 = i * 4; + out[i4..(i4+4)].copy_from_slice(&(a[i] + k).to_lanes()); + out[(i4+4)..(i4+8)].copy_from_slice(&(b[i] + sb).to_lanes()); + out[(i4+8)..(i4+12)].copy_from_slice(&(c[i] + sc).to_lanes()); + out[(i4+12)..(i4+16)].copy_from_slice(&(d[i] + sd[i]).to_lanes()); + } } dispatch!(m, Mach, { From aa5b0e070f3c1088740d3dbb4259572867a2c1ba Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Sun, 12 Sep 2021 13:01:56 -0700 Subject: [PATCH 208/443] Revert "chacha: safer outputting: 0..4 loop" This reverts commit 7d9607a3a3ea6816f7105e508b79cfe664ecc4d4. (Had a bug, after fixing the bug perf was poor) --- rand_chacha/src/guts.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index 0bda609ec69..eeabd9f4c1d 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -159,13 +159,22 @@ fn refill_wide_impl( let sc = m.unpack(state.c); let sd = [m.unpack(state.d), d1, d2, d3]; state.d = d4.into(); - for i in 0..4 { - let i4 = i * 4; - out[i4..(i4+4)].copy_from_slice(&(a[i] + k).to_lanes()); - out[(i4+4)..(i4+8)].copy_from_slice(&(b[i] + sb).to_lanes()); - out[(i4+8)..(i4+12)].copy_from_slice(&(c[i] + sc).to_lanes()); - out[(i4+12)..(i4+16)].copy_from_slice(&(d[i] + sd[i]).to_lanes()); - } + out[0..4].copy_from_slice(&(a[0] + k).to_lanes()); + out[4..8].copy_from_slice(&(b[0] + sb).to_lanes()); + out[8..12].copy_from_slice(&(c[0] + sc).to_lanes()); + out[12..16].copy_from_slice(&(d[0] + sd[0]).to_lanes()); + out[16..20].copy_from_slice(&(a[1] + k).to_lanes()); + out[20..24].copy_from_slice(&(b[1] + sb).to_lanes()); + out[24..28].copy_from_slice(&(c[1] + sc).to_lanes()); + out[28..32].copy_from_slice(&(d[1] + sd[1]).to_lanes()); + out[32..36].copy_from_slice(&(a[2] + k).to_lanes()); + out[36..40].copy_from_slice(&(b[2] + sb).to_lanes()); + out[40..44].copy_from_slice(&(c[2] + sc).to_lanes()); + out[44..48].copy_from_slice(&(d[2] + sd[2]).to_lanes()); + out[48..52].copy_from_slice(&(a[3] + k).to_lanes()); + out[52..56].copy_from_slice(&(b[3] + sb).to_lanes()); + out[56..60].copy_from_slice(&(c[3] + sc).to_lanes()); + out[60..64].copy_from_slice(&(d[3] + sd[3]).to_lanes()); } dispatch!(m, Mach, { From 9972046a118844b9d9ea759e292de08f26761b7d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 13 Sep 2021 09:30:14 +0100 Subject: [PATCH 209/443] fill_via_chunks: make a generic function --- rand_core/src/impls.rs | 76 +++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index f16f5b5b8d2..bd21b7c731e 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -52,36 +52,52 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } } -macro_rules! fill_via_chunks { - ($src:expr, $dst:expr, $ty:ty) => {{ - const SIZE: usize = core::mem::size_of::<$ty>(); - let chunk_size_u8 = min($src.len() * SIZE, $dst.len()); - let chunk_size = (chunk_size_u8 + SIZE - 1) / SIZE; - - if cfg!(target_endian = "little") { - // On LE we can do a simple copy, which is 25-50% faster: - unsafe { - core::ptr::copy_nonoverlapping( - $src.as_ptr() as *const u8, - $dst.as_mut_ptr(), - chunk_size_u8); - } - } else { - // This code is valid on all arches, but slower than the above: - let mut i = 0; - let mut iter = $dst[..chunk_size_u8].chunks_exact_mut(SIZE); - while let Some(chunk) = iter.next() { - chunk.copy_from_slice(&$src[i].to_le_bytes()); - i += 1; - } - let chunk = iter.into_remainder(); - if !chunk.is_empty() { - chunk.copy_from_slice(&$src[i].to_le_bytes()[..chunk.len()]); - } +trait ToLe: Copy { + type Bytes: AsRef<[u8]>; + fn to_le_bytes(self) -> Self::Bytes; +} +impl ToLe for u32 { + type Bytes = [u8; 4]; + fn to_le_bytes(self) -> Self::Bytes { + self.to_le_bytes() + } +} +impl ToLe for u64 { + type Bytes = [u8; 8]; + fn to_le_bytes(self) -> Self::Bytes { + self.to_le_bytes() + } +} + +fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { + let size = core::mem::size_of::(); + let chunk_size_u8 = min(src.len() * size, dest.len()); + let chunk_size = (chunk_size_u8 + size - 1) / size; + + if cfg!(target_endian = "little") { + // On LE we can do a simple copy, which is 25-50% faster: + unsafe { + core::ptr::copy_nonoverlapping( + src.as_ptr() as *const u8, + dest.as_mut_ptr(), + chunk_size_u8, + ); + } + } else { + // This code is valid on all arches, but slower than the above: + let mut i = 0; + let mut iter = dest[..chunk_size_u8].chunks_exact_mut(size); + while let Some(chunk) = iter.next() { + chunk.copy_from_slice(src[i].to_le_bytes().as_ref()); + i += 1; } + let chunk = iter.into_remainder(); + if !chunk.is_empty() { + chunk.copy_from_slice(&src[i].to_le_bytes().as_ref()[..chunk.len()]); + } + } - (chunk_size, chunk_size_u8) - }}; + (chunk_size, chunk_size_u8) } /// Implement `fill_bytes` by reading chunks from the output buffer of a block @@ -115,7 +131,7 @@ macro_rules! fill_via_chunks { /// } /// ``` pub fn fill_via_u32_chunks(src: &[u32], dest: &mut [u8]) -> (usize, usize) { - fill_via_chunks!(src, dest, u32) + fill_via_chunks(src, dest) } /// Implement `fill_bytes` by reading chunks from the output buffer of a block @@ -129,7 +145,7 @@ pub fn fill_via_u32_chunks(src: &[u32], dest: &mut [u8]) -> (usize, usize) { /// /// See `fill_via_u32_chunks` for an example. pub fn fill_via_u64_chunks(src: &[u64], dest: &mut [u8]) -> (usize, usize) { - fill_via_chunks!(src, dest, u64) + fill_via_chunks(src, dest) } /// Implement `next_u32` via `fill_bytes`, little-endian order. From 93ade1a009b66476491f0521a56e5dd97a4791bc Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 14 Sep 2021 08:11:53 +0100 Subject: [PATCH 210/443] fill_via_chunks: better value names --- rand_core/src/impls.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index bd21b7c731e..7c09b5488ac 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -71,8 +71,8 @@ impl ToLe for u64 { fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { let size = core::mem::size_of::(); - let chunk_size_u8 = min(src.len() * size, dest.len()); - let chunk_size = (chunk_size_u8 + size - 1) / size; + let byte_len = min(src.len() * size, dest.len()); + let num_chunks = (byte_len + size - 1) / size; if cfg!(target_endian = "little") { // On LE we can do a simple copy, which is 25-50% faster: @@ -80,13 +80,13 @@ fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { core::ptr::copy_nonoverlapping( src.as_ptr() as *const u8, dest.as_mut_ptr(), - chunk_size_u8, + byte_len, ); } } else { // This code is valid on all arches, but slower than the above: let mut i = 0; - let mut iter = dest[..chunk_size_u8].chunks_exact_mut(size); + let mut iter = dest[..byte_len].chunks_exact_mut(size); while let Some(chunk) = iter.next() { chunk.copy_from_slice(src[i].to_le_bytes().as_ref()); i += 1; @@ -97,7 +97,7 @@ fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { } } - (chunk_size, chunk_size_u8) + (num_chunks, byte_len) } /// Implement `fill_bytes` by reading chunks from the output buffer of a block From 19b7a76481c23497aff33fe7024cac4564d5196e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 14 Sep 2021 08:17:26 +0100 Subject: [PATCH 211/443] fill_via_chunks: make ToLe an unsafe trait --- rand_core/src/impls.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 7c09b5488ac..1105094221e 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -52,17 +52,19 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } } -trait ToLe: Copy { +/// Contract: implementing type must be memory-safe to observe as a byte array +/// (implies no uninitialised padding). +unsafe trait ToLe: Copy { type Bytes: AsRef<[u8]>; fn to_le_bytes(self) -> Self::Bytes; } -impl ToLe for u32 { +unsafe impl ToLe for u32 { type Bytes = [u8; 4]; fn to_le_bytes(self) -> Self::Bytes { self.to_le_bytes() } } -impl ToLe for u64 { +unsafe impl ToLe for u64 { type Bytes = [u8; 8]; fn to_le_bytes(self) -> Self::Bytes { self.to_le_bytes() From 34a8f13d863a79be9d41f2ef63dc3b85b156c9dc Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 15 Sep 2021 09:27:11 +0100 Subject: [PATCH 212/443] Replace ToLe with Observable, including as_byte_slice method --- rand_core/src/impls.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 1105094221e..12838dd138d 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -52,39 +52,44 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } } -/// Contract: implementing type must be memory-safe to observe as a byte array -/// (implies no uninitialised padding). -unsafe trait ToLe: Copy { +trait Observable: Copy { type Bytes: AsRef<[u8]>; fn to_le_bytes(self) -> Self::Bytes; + + // Contract: observing self is memory-safe (implies no uninitialised padding) + fn as_byte_slice(x: &[Self]) -> &[u8]; } -unsafe impl ToLe for u32 { +impl Observable for u32 { type Bytes = [u8; 4]; fn to_le_bytes(self) -> Self::Bytes { self.to_le_bytes() } + fn as_byte_slice(x: &[Self]) -> &[u8] { + let ptr = x.as_ptr() as *const u8; + let len = x.len() * core::mem::size_of::(); + unsafe { core::slice::from_raw_parts(ptr, len) } + } } -unsafe impl ToLe for u64 { +impl Observable for u64 { type Bytes = [u8; 8]; fn to_le_bytes(self) -> Self::Bytes { self.to_le_bytes() } + fn as_byte_slice(x: &[Self]) -> &[u8] { + let ptr = x.as_ptr() as *const u8; + let len = x.len() * core::mem::size_of::(); + unsafe { core::slice::from_raw_parts(ptr, len) } + } } -fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { +fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { let size = core::mem::size_of::(); let byte_len = min(src.len() * size, dest.len()); let num_chunks = (byte_len + size - 1) / size; if cfg!(target_endian = "little") { // On LE we can do a simple copy, which is 25-50% faster: - unsafe { - core::ptr::copy_nonoverlapping( - src.as_ptr() as *const u8, - dest.as_mut_ptr(), - byte_len, - ); - } + dest[..byte_len].copy_from_slice(&T::as_byte_slice(&src[..num_chunks])[..byte_len]); } else { // This code is valid on all arches, but slower than the above: let mut i = 0; From a9401e16b707c6554632d5d7b6af347e4ea748f9 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 15 Sep 2021 18:55:08 +0200 Subject: [PATCH 213/443] Mention that `Fill` supports floats --- src/distributions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index e3086680b71..05ca80606b0 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -158,7 +158,7 @@ use crate::Rng; /// * Tuples (up to 12 elements): each element is generated sequentially. /// * Arrays (up to 32 elements): each element is generated sequentially; /// see also [`Rng::fill`] which supports arbitrary array length for integer -/// types and tends to be faster for `u32` and smaller types. +/// and float types and tends to be faster for `u32` and smaller types. /// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support /// arrays larger than 32 elements. /// Note that [`Rng::fill`] and `Standard`'s array support are *not* equivalent: From 48463e05b91037a7b49d98c394430f2eb1cb2c56 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 17 Sep 2021 19:38:40 +0200 Subject: [PATCH 214/443] Prepare rand_distr 0.4.2 release --- rand_distr/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 861f384fe67..b93933e9ec9 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [0.4.2] - 2021-09-18 - New `Zeta` and `Zipf` distributions (#1136) - New `SkewNormal` distribution (#1149) - New `Gumbel` and `Frechet` distributions (#1168, #1171) From eac985fe73cc4fa89eaabe71eec4c3b013bbd56b Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 17 Sep 2021 21:35:48 +0200 Subject: [PATCH 215/443] Fix spelling --- rand_core/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 1ae692cc701..fccbbd09c25 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -448,10 +448,10 @@ impl std::io::Read for dyn RngCore { } } -// Implement `CryptoRng` for references to an `CryptoRng`. +// Implement `CryptoRng` for references to a `CryptoRng`. impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {} -// Implement `CryptoRng` for boxed references to an `CryptoRng`. +// Implement `CryptoRng` for boxed references to a `CryptoRng`. #[cfg(feature = "alloc")] impl CryptoRng for Box {} From 51d1365370893297f79b187b0f717da4ef1645aa Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 17 Sep 2021 21:53:32 +0200 Subject: [PATCH 216/443] rand_core: Add `CryptoRngCore` to support `CryptoRng` trait objects Fixes #1143. --- rand_core/src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index fccbbd09c25..cdc130067e2 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -208,6 +208,20 @@ pub trait RngCore { /// [`BlockRngCore`]: block::BlockRngCore pub trait CryptoRng {} +/// An extension trait to support trait objects that implement [`RngCore`] and +/// [`CryptoRng`]. Upcasting to [`RngCore`] is supported via the +/// [`CryptoRngCore::upcast`] method. +pub trait CryptoRngCore: RngCore { + /// Upcast to an [`RngCore`] trait object. + fn upcast(&mut self) -> &mut dyn RngCore; +} + +impl CryptoRngCore for T { + fn upcast(&mut self) -> &mut dyn RngCore { + self + } +} + /// A random number generator that can be explicitly seeded. /// /// This trait encapsulates the low-level functionality common to all From fe236c52f32b666da1050493efa74db89a633bfb Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 17 Sep 2021 21:59:16 +0200 Subject: [PATCH 217/443] rand_chacha: Add test for trait object upcasting --- rand_chacha/src/chacha.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index fce8e493796..956fc1c38fc 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -623,4 +623,15 @@ mod test { rng.set_word_pos(0); assert_eq!(rng.get_word_pos(), 0); } + + #[test] + fn test_trait_objects() { + use rand_core::CryptoRngCore; + + let rng = &mut ChaChaRng::from_seed(Default::default()) as &mut dyn CryptoRngCore; + let r1 = rng.next_u64(); + let rng: &mut dyn RngCore = rng.upcast(); + let r2 = rng.next_u64(); + assert_ne!(r1, r2); + } } From da917e673f49116f2f1bf37ea1ee5a99fe3c8c5d Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Sat, 18 Sep 2021 15:45:42 +0200 Subject: [PATCH 218/443] rand_distr: Bump version --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 680c86966a9..8a9638c80fb 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.4.1" +version = "0.4.2" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From c797f070b125084d727dc0ba5104bbdae966ba78 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 20 Sep 2021 14:08:39 +0200 Subject: [PATCH 219/443] rand_core: Rename `CryptoRngCore::upcast` to `as_rngcore` --- rand_chacha/src/chacha.rs | 2 +- rand_core/src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 956fc1c38fc..ad74b35f62b 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -630,7 +630,7 @@ mod test { let rng = &mut ChaChaRng::from_seed(Default::default()) as &mut dyn CryptoRngCore; let r1 = rng.next_u64(); - let rng: &mut dyn RngCore = rng.upcast(); + let rng: &mut dyn RngCore = rng.as_rngcore(); let r2 = rng.next_u64(); assert_ne!(r1, r2); } diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index cdc130067e2..9ac353b4526 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -210,14 +210,14 @@ pub trait CryptoRng {} /// An extension trait to support trait objects that implement [`RngCore`] and /// [`CryptoRng`]. Upcasting to [`RngCore`] is supported via the -/// [`CryptoRngCore::upcast`] method. +/// [`CryptoRngCore::as_rngcore`] method. pub trait CryptoRngCore: RngCore { /// Upcast to an [`RngCore`] trait object. - fn upcast(&mut self) -> &mut dyn RngCore; + fn as_rngcore(&mut self) -> &mut dyn RngCore; } impl CryptoRngCore for T { - fn upcast(&mut self) -> &mut dyn RngCore { + fn as_rngcore(&mut self) -> &mut dyn RngCore { self } } From a4fa0771a2605d6759a8c0fe252534c1f54fad6e Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 20 Sep 2021 14:27:14 +0200 Subject: [PATCH 220/443] CryptoRngCore: Improve docs --- rand_core/src/lib.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 9ac353b4526..fdbf6675b96 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -208,9 +208,24 @@ pub trait RngCore { /// [`BlockRngCore`]: block::BlockRngCore pub trait CryptoRng {} -/// An extension trait to support trait objects that implement [`RngCore`] and -/// [`CryptoRng`]. Upcasting to [`RngCore`] is supported via the -/// [`CryptoRngCore::as_rngcore`] method. +/// An extension trait that is automatically implemented for any type +/// implementing [`RngCore`] and [`CryptoRng`]. +/// +/// It may be used as a trait object, and supports upcasting to [`RngCore`] via +/// the [`CryptoRngCore::as_rngcore`] method. +/// +/// # Example +/// +/// ``` +/// use rand_core::CryptoRngCore; +/// +/// #[allow(unused)] +/// fn make_token(rng: &mut dyn CryptoRngCore) -> [u8; 32] { +/// let mut buf = [0u8; 32]; +/// rng.fill_bytes(&mut buf); +/// buf +/// } +/// ``` pub trait CryptoRngCore: RngCore { /// Upcast to an [`RngCore`] trait object. fn as_rngcore(&mut self) -> &mut dyn RngCore; From d8cde3fe579f9aa73c40ad945d053174314c60bf Mon Sep 17 00:00:00 2001 From: r00ster Date: Tue, 12 Oct 2021 21:07:36 +0200 Subject: [PATCH 221/443] Fix typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6834824a1e..72954a7b90e 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Rand is built with these features enabled by default: Optionally, the following dependencies can be enabled: -- `log` enables logging via the `log` crate` crate +- `log` enables logging via the `log` crate Additionally, these features configure Rand: From 6e6cc35d993bcd95be2817b90539c74eff8eb30a Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Wed, 13 Oct 2021 12:46:03 -0700 Subject: [PATCH 222/443] rand_chacha: improve little-endian performance incrementing pos counter On little-endian platforms we can use native vector operations to increment the pos counter, because it is packed little-endian into the state vector. --- rand_chacha/Cargo.toml | 2 +- rand_chacha/src/guts.rs | 67 ++++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index f99d1967133..c4f5c113142 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -16,7 +16,7 @@ edition = "2018" [dependencies] rand_core = { path = "../rand_core", version = "0.6.0" } -ppv-lite86 = { version = "0.2.8", default-features = false, features = ["simd"] } +ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index eeabd9f4c1d..cf1a31791bb 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -73,12 +73,6 @@ impl ChaCha { init_chacha(key, nonce) } - #[inline(always)] - fn pos64(&self, m: M) -> u64 { - let d: M::u32x4 = m.unpack(self.d); - ((d.extract(1) as u64) << 32) | d.extract(0) as u64 - } - /// Produce 4 blocks of output, advancing the state #[inline(always)] pub fn refill4(&mut self, drounds: u32, out: &mut [u32; BUFSZ]) { @@ -111,44 +105,63 @@ impl ChaCha { } } -#[allow(clippy::many_single_char_names)] +// This implementation is platform-independent. #[inline(always)] -fn refill_wide_impl( - m: Mach, state: &mut ChaCha, drounds: u32, out: &mut [u32; BUFSZ], -) { - let k = m.vec([0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]); - let mut pos = state.pos64(m); - let d0: Mach::u32x4 = m.unpack(state.d); +#[cfg(target_endian = "big")] +fn add_pos(_m: Mach, d0: Mach::u32x4, i: u64) -> Mach::u32x4 { + let pos0 = ((d0.extract(1) as u64) << 32) | d0.extract(0) as u64; + let pos = pos0.wrapping_add(i); + d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0) +} +#[inline(always)] +#[cfg(target_endian = "big")] +fn d0123(m: Mach, d: vec128_storage) -> Mach::u32x4x4 { + let d0: Mach::u32x4 = m.unpack(d); + let mut pos = ((d0.extract(1) as u64) << 32) | d0.extract(0) as u64; pos = pos.wrapping_add(1); let d1 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); pos = pos.wrapping_add(1); let d2 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); pos = pos.wrapping_add(1); let d3 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); + Mach::u32x4x4::from_lanes([d0, d1, d2, d3]) +} + +// Pos is packed into the state vectors as a little-endian u64, +// so on LE platforms we can use native vector ops to increment it. +#[inline(always)] +#[cfg(target_endian = "little")] +fn add_pos(m: Mach, d: Mach::u32x4, i: u64) -> Mach::u32x4 { + let d0: Mach::u64x2 = m.unpack(d.into()); + let incr = m.vec([i, 0]); + m.unpack((d0 + incr).into()) +} +#[inline(always)] +#[cfg(target_endian = "little")] +fn d0123(m: Mach, d: vec128_storage) -> Mach::u32x4x4 { + let d0: Mach::u64x2 = m.unpack(d); + let incr = Mach::u64x2x4::from_lanes([m.vec([0, 0]), m.vec([1, 0]), m.vec([2, 0]), m.vec([3, 0])]); + m.unpack((Mach::u64x2x4::from_lanes([d0, d0, d0, d0]) + incr).into()) +} +#[allow(clippy::many_single_char_names)] +#[inline(always)] +fn refill_wide_impl( + m: Mach, state: &mut ChaCha, drounds: u32, out: &mut [u32; BUFSZ], +) { + let k = m.vec([0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]); let b = m.unpack(state.b); let c = m.unpack(state.c); let mut x = State { a: Mach::u32x4x4::from_lanes([k, k, k, k]), b: Mach::u32x4x4::from_lanes([b, b, b, b]), c: Mach::u32x4x4::from_lanes([c, c, c, c]), - d: m.unpack(Mach::u32x4x4::from_lanes([d0, d1, d2, d3]).into()), + d: d0123(m, state.d), }; for _ in 0..drounds { x = round(x); x = undiagonalize(round(diagonalize(x))); } - let mut pos = state.pos64(m); - let d0: Mach::u32x4 = m.unpack(state.d); - pos = pos.wrapping_add(1); - let d1 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); - pos = pos.wrapping_add(1); - let d2 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); - pos = pos.wrapping_add(1); - let d3 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); - pos = pos.wrapping_add(1); - let d4 = d0.insert((pos >> 32) as u32, 1).insert(pos as u32, 0); - let (a, b, c, d) = ( x.a.to_lanes(), x.b.to_lanes(), @@ -157,8 +170,8 @@ fn refill_wide_impl( ); let sb = m.unpack(state.b); let sc = m.unpack(state.c); - let sd = [m.unpack(state.d), d1, d2, d3]; - state.d = d4.into(); + let sd = d0123(m, state.d).to_lanes(); + state.d = add_pos(m, sd[0], 4).into(); out[0..4].copy_from_slice(&(a[0] + k).to_lanes()); out[4..8].copy_from_slice(&(b[0] + sb).to_lanes()); out[8..12].copy_from_slice(&(c[0] + sc).to_lanes()); From 6455dc47e1df8c7034a18654a0c3365dbe5d788a Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Wed, 13 Oct 2021 13:04:56 -0700 Subject: [PATCH 223/443] rand_chacha: optimize outputting Improve AVX2 vectorizability of copying results to buffer. Performance gain measured at 15% (ChaCha20) to 37% (ChaCha8). --- rand_chacha/src/guts.rs | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index cf1a31791bb..797ded6fa73 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -12,7 +12,7 @@ use ppv_lite86::{dispatch, dispatch_light128}; pub use ppv_lite86::Machine; -use ppv_lite86::{vec128_storage, ArithOps, BitOps32, LaneWords4, MultiLane, StoreBytes, Vec4}; +use ppv_lite86::{vec128_storage, ArithOps, BitOps32, LaneWords4, MultiLane, StoreBytes, Vec4, Vec4Ext, Vector}; pub(crate) const BLOCK: usize = 16; pub(crate) const BLOCK64: u64 = BLOCK as u64; @@ -162,32 +162,18 @@ fn refill_wide_impl( x = round(x); x = undiagonalize(round(diagonalize(x))); } - let (a, b, c, d) = ( - x.a.to_lanes(), - x.b.to_lanes(), - x.c.to_lanes(), - x.d.to_lanes(), - ); + let kk = Mach::u32x4x4::from_lanes([k, k, k, k]); let sb = m.unpack(state.b); + let sb = Mach::u32x4x4::from_lanes([sb, sb, sb, sb]); let sc = m.unpack(state.c); - let sd = d0123(m, state.d).to_lanes(); - state.d = add_pos(m, sd[0], 4).into(); - out[0..4].copy_from_slice(&(a[0] + k).to_lanes()); - out[4..8].copy_from_slice(&(b[0] + sb).to_lanes()); - out[8..12].copy_from_slice(&(c[0] + sc).to_lanes()); - out[12..16].copy_from_slice(&(d[0] + sd[0]).to_lanes()); - out[16..20].copy_from_slice(&(a[1] + k).to_lanes()); - out[20..24].copy_from_slice(&(b[1] + sb).to_lanes()); - out[24..28].copy_from_slice(&(c[1] + sc).to_lanes()); - out[28..32].copy_from_slice(&(d[1] + sd[1]).to_lanes()); - out[32..36].copy_from_slice(&(a[2] + k).to_lanes()); - out[36..40].copy_from_slice(&(b[2] + sb).to_lanes()); - out[40..44].copy_from_slice(&(c[2] + sc).to_lanes()); - out[44..48].copy_from_slice(&(d[2] + sd[2]).to_lanes()); - out[48..52].copy_from_slice(&(a[3] + k).to_lanes()); - out[52..56].copy_from_slice(&(b[3] + sb).to_lanes()); - out[56..60].copy_from_slice(&(c[3] + sc).to_lanes()); - out[60..64].copy_from_slice(&(d[3] + sd[3]).to_lanes()); + let sc = Mach::u32x4x4::from_lanes([sc, sc, sc, sc]); + let sd = d0123(m, state.d); + let results = Mach::u32x4x4::transpose4(x.a + kk, x.b + sb, x.c + sc, x.d + sd); + out[0..16].copy_from_slice(&results.0.to_scalars()); + out[16..32].copy_from_slice(&results.1.to_scalars()); + out[32..48].copy_from_slice(&results.2.to_scalars()); + out[48..64].copy_from_slice(&results.3.to_scalars()); + state.d = add_pos(m, sd.to_lanes()[0], 4).into(); } dispatch!(m, Mach, { From bd0038d7676cf2cea87ef2409fc3062e7c0501ba Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 21 Oct 2021 16:23:14 +0200 Subject: [PATCH 224/443] Fix clippy warnings --- rand_core/src/impls.rs | 2 +- rand_distr/src/cauchy.rs | 4 ++-- rand_distr/src/lib.rs | 12 +++++------- rand_distr/tests/pdf.rs | 8 ++------ 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 12838dd138d..ba990c465a2 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -94,7 +94,7 @@ fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) // This code is valid on all arches, but slower than the above: let mut i = 0; let mut iter = dest[..byte_len].chunks_exact_mut(size); - while let Some(chunk) = iter.next() { + for chunk in iter.by_ref() { chunk.copy_from_slice(src[i].to_le_bytes().as_ref()); i += 1; } diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 49b121c41d2..8c2ccdd3f7b 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -90,8 +90,8 @@ where F: Float + FloatConst, Standard: Distribution mod test { use super::*; - fn median(mut numbers: &mut [f64]) -> f64 { - sort(&mut numbers); + fn median(numbers: &mut [f64]) -> f64 { + sort(numbers); let mid = numbers.len() / 2; numbers[mid] } diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index f00f4c819f3..6d8d81bd2f3 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -173,13 +173,11 @@ mod test { macro_rules! assert_almost_eq { ($a:expr, $b:expr, $prec:expr) => { let diff = ($a - $b).abs(); - if diff > $prec { - panic!( - "assertion failed: `abs(left - right) = {:.1e} < {:e}`, \ - (left: `{}`, right: `{}`)", - diff, $prec, $a, $b - ); - } + assert!(diff <= $prec, + "assertion failed: `abs(left - right) = {:.1e} < {:e}`, \ + (left: `{}`, right: `{}`)", + diff, $prec, $a, $b + ); }; } } diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 198b690d2ef..eb766142752 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -91,9 +91,7 @@ fn normal() { for (&d, &e) in diff.iter().zip(expected_error.iter()) { // Difference larger than 3 standard deviations or cutoff let tol = (3. * e).max(1e-4); - if d > tol { - panic!("Difference = {} * tol", d / tol); - } + assert!(d <= tol, "Difference = {} * tol", d / tol); } } @@ -176,8 +174,6 @@ fn skew_normal() { for (&d, &e) in diff.iter().zip(expected_error.iter()) { // Difference larger than 3 standard deviations or cutoff let tol = (3. * e).max(1e-4); - if d > tol { - panic!("Difference = {} * tol", d / tol); - } + assert!(d <= tol, "Difference = {} * tol", d / tol); } } From c1e859f8187e9ef7b23c241bd2c5d5dba587551b Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 21 Oct 2021 20:35:33 +0200 Subject: [PATCH 225/443] Avoid unnecessary `by_ref` --- rand_core/src/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index ba990c465a2..4b7688c5c80 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -94,7 +94,7 @@ fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) // This code is valid on all arches, but slower than the above: let mut i = 0; let mut iter = dest[..byte_len].chunks_exact_mut(size); - for chunk in iter.by_ref() { + for chunk in &mut iter { chunk.copy_from_slice(src[i].to_le_bytes().as_ref()); i += 1; } From fb7af73433445e081cb6da766b510fe18f641cbd Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Fri, 22 Oct 2021 09:18:15 -0700 Subject: [PATCH 226/443] rand_chacha: update changelog --- rand_chacha/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index 1367b34a3d7..7ef621f6781 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Made `rand_chacha` propagate the `std` feature down to `rand_core` +- Performance improvements for AVX2: ~4-7% ## [0.3.1] - 2021-06-09 - add getters corresponding to existing setters: `get_seed`, `get_stream` (#1124) From 3e65a15cab6a7880d8db5bfe30444a2ab075ed99 Mon Sep 17 00:00:00 2001 From: Sacha Muszlak Date: Thu, 4 Nov 2021 15:15:34 +0100 Subject: [PATCH 227/443] updated version in Cargo.toml example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72954a7b90e..44c2e4d518e 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -rand = "0.8.0" +rand = "0.8.4" ``` To get started using Rand, see [The Book](https://rust-random.github.io/book). From 743661e2f56db28a9a2e26def80363430f47bf45 Mon Sep 17 00:00:00 2001 From: Cheng XU Date: Tue, 23 Nov 2021 17:46:33 -0800 Subject: [PATCH 228/443] rand: fix incorrect example in Uniform The current example to implement `SampleUniform` for custom types is incorrect. It mistakenly forwards the implementation of `UniformSampler::new_inclusive` to `UniformSampler::new`. --- src/distributions/uniform.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 096009f9ecb..482461f0f13 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -80,7 +80,10 @@ //! where B1: SampleBorrow + Sized, //! B2: SampleBorrow + Sized //! { -//! UniformSampler::new(low, high) +//! UniformMyF32(UniformFloat::::new_inclusive( +//! low.borrow().0, +//! high.borrow().0, +//! )) //! } //! fn sample(&self, rng: &mut R) -> Self::X { //! MyF32(self.0.sample(rng)) From b0d7833b55238f7096fb30af43f28ca7362feea8 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Tue, 16 Nov 2021 19:55:52 +0100 Subject: [PATCH 229/443] Replace the open-coded fill_bytes_impl by rand_core::impls::fill_bytes_via_next. --- rand_pcg/src/pcg128.rs | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index 0cbb96a42b3..f8166f780d9 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -14,7 +14,7 @@ const MULTIPLIER: u128 = 0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645; use core::fmt; -use rand_core::{le, Error, RngCore, SeedableRng}; +use rand_core::{impls, le, Error, RngCore, SeedableRng}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// A PCG random number generator (XSL RR 128/64 (LCG) variant). @@ -146,7 +146,7 @@ impl RngCore for Lcg128Xsl64 { #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { - fill_bytes_impl(self, dest) + impls::fill_bytes_via_next(self, dest) } #[inline] @@ -237,8 +237,7 @@ impl SeedableRng for Mcg128Xsl64 { // Read as if a little-endian u128 value: let mut seed_u64 = [0u64; 2]; le::read_u64_into(&seed, &mut seed_u64); - let state = u128::from(seed_u64[0]) | - u128::from(seed_u64[1]) << 64; + let state = u128::from(seed_u64[0]) | u128::from(seed_u64[1]) << 64; Mcg128Xsl64::new(state) } } @@ -257,7 +256,7 @@ impl RngCore for Mcg128Xsl64 { #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { - fill_bytes_impl(self, dest) + impls::fill_bytes_via_next(self, dest) } #[inline] @@ -278,19 +277,3 @@ fn output_xsl_rr(state: u128) -> u64 { let xsl = ((state >> XSHIFT) as u64) ^ (state as u64); xsl.rotate_right(rot) } - -#[inline(always)] -fn fill_bytes_impl(rng: &mut R, dest: &mut [u8]) { - let mut left = dest; - while left.len() >= 8 { - let (l, r) = { left }.split_at_mut(8); - left = r; - let chunk: [u8; 8] = rng.next_u64().to_le_bytes(); - l.copy_from_slice(&chunk); - } - let n = left.len(); - if n > 0 { - let chunk: [u8; 8] = rng.next_u64().to_le_bytes(); - left.copy_from_slice(&chunk[..n]); - } -} From ece8e48905af362fcf0ca8925da065ddc45d0f19 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Thu, 18 Nov 2021 12:09:27 +0100 Subject: [PATCH 230/443] Add Lcg128CmDxsm64 generator compatible with NumPy's PCG64DXSM. --- rand_pcg/CHANGELOG.md | 3 + rand_pcg/src/lib.rs | 2 + rand_pcg/src/pcg128cm.rs | 182 +++++++++++++++++++++++++++++++ rand_pcg/tests/lcg128cmdxsm64.rs | 77 +++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 rand_pcg/src/pcg128cm.rs create mode 100644 rand_pcg/tests/lcg128cmdxsm64.rs diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index 7464b301001..8bc112adabd 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +- Add `Lcg128CmDxsm64` generator compatible with NumPy's `PCG64DXSM` (#1202) + ## [0.3.1] - 2021-06-15 - Add `advance` methods to RNGs (#1111) - Document dependencies between streams (#1122) diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index b14840af2c8..9d0209d14fe 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -38,7 +38,9 @@ #![no_std] mod pcg128; +mod pcg128cm; mod pcg64; pub use self::pcg128::{Lcg128Xsl64, Mcg128Xsl64, Pcg64, Pcg64Mcg}; +pub use self::pcg128cm::{Lcg128CmDxsm64, Pcg64Dxsm}; pub use self::pcg64::{Lcg64Xsh32, Pcg32}; diff --git a/rand_pcg/src/pcg128cm.rs b/rand_pcg/src/pcg128cm.rs new file mode 100644 index 00000000000..7ac5187e4e0 --- /dev/null +++ b/rand_pcg/src/pcg128cm.rs @@ -0,0 +1,182 @@ +// Copyright 2018-2021 Developers of the Rand project. +// Copyright 2017 Paul Dicker. +// Copyright 2014-2017, 2019 Melissa O'Neill and PCG Project contributors +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! PCG random number generators + +// This is the cheap multiplier used by PCG for 128-bit state. +const MULTIPLIER: u64 = 15750249268501108917; + +use core::fmt; +use rand_core::{impls, le, Error, RngCore, SeedableRng}; +#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; + +/// A PCG random number generator (CM DXSM 128/64 (LCG) variant). +/// +/// Permuted Congruential Generator with 128-bit state, internal Linear +/// Congruential Generator, and 64-bit output via "double xorshift multiply" +/// output function. +/// +/// This is a 128-bit LCG with explicitly chosen stream with the PCG-DXSM +/// output function. This corresponds to `pcg_engines::cm_setseq_dxsm_128_64` +/// from pcg_cpp and `PCG64DXSM` from NumPy. +/// +/// Despite the name, this implementation uses 32 bytes (256 bit) space +/// comprising 128 bits of state and 128 bits stream selector. These are both +/// set by `SeedableRng`, using a 256-bit seed. +/// +/// Note that while two generators with different stream parameter may be +/// closely correlated, this is [mitigated][upgrading-pcg64] by the DXSM output function. +/// +/// [upgrading-pcg64]: https://numpy.org/doc/stable/reference/random/upgrading-pcg64.html +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Lcg128CmDxsm64 { + state: u128, + increment: u128, +} + +/// [`Lcg128CmDxsm64`] is also known as `PCG64DXSM`. +pub type Pcg64Dxsm = Lcg128CmDxsm64; + +impl Lcg128CmDxsm64 { + /// Multi-step advance functions (jump-ahead, jump-back) + /// + /// The method used here is based on Brown, "Random Number Generation + /// with Arbitrary Stride,", Transactions of the American Nuclear + /// Society (Nov. 1994). The algorithm is very similar to fast + /// exponentiation. + /// + /// Even though delta is an unsigned integer, we can pass a + /// signed integer to go backwards, it just goes "the long way round". + /// + /// Using this function is equivalent to calling `next_64()` `delta` + /// number of times. + #[inline] + pub fn advance(&mut self, delta: u128) { + let mut acc_mult: u128 = 1; + let mut acc_plus: u128 = 0; + let mut cur_mult = MULTIPLIER as u128; + let mut cur_plus = self.increment; + let mut mdelta = delta; + + while mdelta > 0 { + if (mdelta & 1) != 0 { + acc_mult = acc_mult.wrapping_mul(cur_mult); + acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus); + } + cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus); + cur_mult = cur_mult.wrapping_mul(cur_mult); + mdelta /= 2; + } + self.state = acc_mult.wrapping_mul(self.state).wrapping_add(acc_plus); + } + + /// Construct an instance compatible with PCG seed and stream. + /// + /// Note that the highest bit of the `stream` parameter is discarded + /// to simplify upholding internal invariants. + /// + /// Note that while two generators with different stream parameter may be + /// closely correlated, this is [mitigated][upgrading-pcg64] by the DXSM output function. + /// + /// PCG specifies the following default values for both parameters: + /// + /// - `state = 0xcafef00dd15ea5e5` + /// - `stream = 0xa02bdbf7bb3c0a7ac28fa16a64abf96` + /// + /// [upgrading-pcg64]: https://numpy.org/doc/stable/reference/random/upgrading-pcg64.html + pub fn new(state: u128, stream: u128) -> Self { + // The increment must be odd, hence we discard one bit: + let increment = (stream << 1) | 1; + Self::from_state_incr(state, increment) + } + + #[inline] + fn from_state_incr(state: u128, increment: u128) -> Self { + let mut pcg = Self { state, increment }; + // Move away from initial value: + pcg.state = pcg.state.wrapping_add(pcg.increment); + pcg.step(); + pcg + } + + #[inline(always)] + fn step(&mut self) { + // prepare the LCG for the next round + self.state = self + .state + .wrapping_mul(MULTIPLIER as u128) + .wrapping_add(self.increment); + } +} + +// Custom Debug implementation that does not expose the internal state +impl fmt::Debug for Lcg128CmDxsm64 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Lcg128CmDxsm64 {{}}") + } +} + +impl SeedableRng for Lcg128CmDxsm64 { + type Seed = [u8; 32]; + + /// We use a single 255-bit seed to initialise the state and select a stream. + /// One `seed` bit (lowest bit of `seed[8]`) is ignored. + fn from_seed(seed: Self::Seed) -> Self { + let mut seed_u64 = [0u64; 4]; + le::read_u64_into(&seed, &mut seed_u64); + let state = u128::from(seed_u64[0]) | (u128::from(seed_u64[1]) << 64); + let incr = u128::from(seed_u64[2]) | (u128::from(seed_u64[3]) << 64); + + // The increment must be odd, hence we discard one bit: + Self::from_state_incr(state, incr | 1) + } +} + +impl RngCore for Lcg128CmDxsm64 { + #[inline] + fn next_u32(&mut self) -> u32 { + self.next_u64() as u32 + } + + #[inline] + fn next_u64(&mut self) -> u64 { + let val = output_dxsm(self.state); + self.step(); + val + } + + #[inline] + fn fill_bytes(&mut self, dest: &mut [u8]) { + impls::fill_bytes_via_next(self, dest) + } + + #[inline] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +#[inline(always)] +fn output_dxsm(state: u128) -> u64 { + // See https://github.com/imneme/pcg-cpp/blob/ffd522e7188bef30a00c74dc7eb9de5faff90092/include/pcg_random.hpp#L1016 + // for a short discussion of the construction and its original implementation. + let mut hi = (state >> 64) as u64; + let mut lo = state as u64; + + lo |= 1; + hi ^= hi >> 32; + hi = hi.wrapping_mul(MULTIPLIER); + hi ^= hi >> 48; + hi = hi.wrapping_mul(lo); + + hi +} diff --git a/rand_pcg/tests/lcg128cmdxsm64.rs b/rand_pcg/tests/lcg128cmdxsm64.rs new file mode 100644 index 00000000000..b254b4fac66 --- /dev/null +++ b/rand_pcg/tests/lcg128cmdxsm64.rs @@ -0,0 +1,77 @@ +use rand_core::{RngCore, SeedableRng}; +use rand_pcg::{Lcg128CmDxsm64, Pcg64Dxsm}; + +#[test] +fn test_lcg128cmdxsm64_advancing() { + for seed in 0..20 { + let mut rng1 = Lcg128CmDxsm64::seed_from_u64(seed); + let mut rng2 = rng1.clone(); + for _ in 0..20 { + rng1.next_u64(); + } + rng2.advance(20); + assert_eq!(rng1, rng2); + } +} + +#[test] +fn test_lcg128cmdxsm64_construction() { + // Test that various construction techniques produce a working RNG. + #[rustfmt::skip] + let seed = [1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16, + 17,18,19,20, 21,22,23,24, 25,26,27,28, 29,30,31,32]; + let mut rng1 = Lcg128CmDxsm64::from_seed(seed); + assert_eq!(rng1.next_u64(), 12201417210360370199); + + let mut rng2 = Lcg128CmDxsm64::from_rng(&mut rng1).unwrap(); + assert_eq!(rng2.next_u64(), 11487972556150888383); + + let mut rng3 = Lcg128CmDxsm64::seed_from_u64(0); + assert_eq!(rng3.next_u64(), 4111470453933123814); + + // This is the same as Lcg128CmDxsm64, so we only have a single test: + let mut rng4 = Pcg64Dxsm::seed_from_u64(0); + assert_eq!(rng4.next_u64(), 4111470453933123814); +} + +#[test] +fn test_lcg128cmdxsm64_reference() { + // Numbers determined using `pcg_engines::cm_setseq_dxsm_128_64` from pcg-cpp. + let mut rng = Lcg128CmDxsm64::new(42, 54); + + let mut results = [0u64; 6]; + for i in results.iter_mut() { + *i = rng.next_u64(); + } + let expected: [u64; 6] = [ + 17331114245835578256, + 10267467544499227306, + 9726600296081716989, + 10165951391103677450, + 12131334649314727261, + 10134094537930450875, + ]; + assert_eq!(results, expected); +} + +#[cfg(feature = "serde1")] +#[test] +fn test_lcg128cmdxsm64_serde() { + use bincode; + use std::io::{BufReader, BufWriter}; + + let mut rng = Lcg128CmDxsm64::seed_from_u64(0); + + let buf: Vec = Vec::new(); + let mut buf = BufWriter::new(buf); + bincode::serialize_into(&mut buf, &rng).expect("Could not serialize"); + + let buf = buf.into_inner().unwrap(); + let mut read = BufReader::new(&buf[..]); + let mut deserialized: Lcg128CmDxsm64 = + bincode::deserialize_from(&mut read).expect("Could not deserialize"); + + for _ in 0..16 { + assert_eq!(rng.next_u64(), deserialized.next_u64()); + } +} From 6107f93e813d84ff265c71bf3f3818bb7086e3bb Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Fri, 19 Nov 2021 07:41:39 +0100 Subject: [PATCH 231/443] Apply the same copy-editing to the existing generators as was discussed for Lcg128CmDxsm64. --- rand_pcg/src/pcg128.rs | 7 +++++-- rand_pcg/src/pcg64.rs | 7 +++++-- rand_pcg/tests/lcg128xsl64.rs | 2 +- rand_pcg/tests/lcg64xsh32.rs | 2 +- rand_pcg/tests/mcg128xsl64.rs | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index f8166f780d9..df2025dc444 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -77,6 +77,9 @@ impl Lcg128Xsl64 { /// Construct an instance compatible with PCG seed and stream. /// + /// Note that the highest bit of the `stream` parameter is discarded + /// to simplify upholding internal invariants. + /// /// Note that two generators with different stream parameters may be closely /// correlated. /// @@ -116,11 +119,11 @@ impl fmt::Debug for Lcg128Xsl64 { } } -/// We use a single 255-bit seed to initialise the state and select a stream. -/// One `seed` bit (lowest bit of `seed[8]`) is ignored. impl SeedableRng for Lcg128Xsl64 { type Seed = [u8; 32]; + /// We use a single 255-bit seed to initialise the state and select a stream. + /// One `seed` bit (lowest bit of `seed[8]`) is ignored. fn from_seed(seed: Self::Seed) -> Self { let mut seed_u64 = [0u64; 4]; le::read_u64_into(&seed, &mut seed_u64); diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index 5ab945a0f01..365f1c0b117 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -77,6 +77,9 @@ impl Lcg64Xsh32 { /// Construct an instance compatible with PCG seed and stream. /// + /// Note that the highest bit of the `stream` parameter is discarded + /// to simplify upholding internal invariants. + /// /// Note that two generators with different stream parameters may be closely /// correlated. /// @@ -117,11 +120,11 @@ impl fmt::Debug for Lcg64Xsh32 { } } -/// We use a single 127-bit seed to initialise the state and select a stream. -/// One `seed` bit (lowest bit of `seed[8]`) is ignored. impl SeedableRng for Lcg64Xsh32 { type Seed = [u8; 16]; + /// We use a single 127-bit seed to initialise the state and select a stream. + /// One `seed` bit (lowest bit of `seed[8]`) is ignored. fn from_seed(seed: Self::Seed) -> Self { let mut seed_u64 = [0u64; 2]; le::read_u64_into(&seed, &mut seed_u64); diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index 57f559a1390..31eada442eb 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -35,7 +35,7 @@ fn test_lcg128xsl64_construction() { } #[test] -fn test_lcg128xsl64_true_values() { +fn test_lcg128xsl64_reference() { // Numbers copied from official test suite (C version). let mut rng = Lcg128Xsl64::new(42, 54); diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index 1efe5c58c33..9c181ee3a45 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -33,7 +33,7 @@ fn test_lcg64xsh32_construction() { } #[test] -fn test_lcg64xsh32_true_values() { +fn test_lcg64xsh32_reference() { // Numbers copied from official test suite. let mut rng = Lcg64Xsh32::new(42, 54); diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index c508a8ffa3d..1f352b6e879 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -33,7 +33,7 @@ fn test_mcg128xsl64_construction() { } #[test] -fn test_mcg128xsl64_true_values() { +fn test_mcg128xsl64_reference() { // Numbers copied from official test suite (C version). let mut rng = Mcg128Xsl64::new(42); From f44ea42089688a30e593cf4d8b641b2e20e29bc6 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Mon, 6 Dec 2021 13:41:05 +0100 Subject: [PATCH 232/443] Extend generator benchmarks to include the newly added Pcg64Dxsm variant. --- benches/generators.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/benches/generators.rs b/benches/generators.rs index 65305a18e1f..96fa302b6a0 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -21,7 +21,7 @@ use rand::prelude::*; use rand::rngs::adapter::ReseedingRng; use rand::rngs::{mock::StepRng, OsRng}; use rand_chacha::{ChaCha12Rng, ChaCha20Core, ChaCha20Rng, ChaCha8Rng}; -use rand_pcg::{Pcg32, Pcg64, Pcg64Mcg}; +use rand_pcg::{Pcg32, Pcg64, Pcg64Mcg, Pcg64Dxsm}; macro_rules! gen_bytes { ($fnn:ident, $gen:expr) => { @@ -44,6 +44,7 @@ gen_bytes!(gen_bytes_step, StepRng::new(0, 1)); gen_bytes!(gen_bytes_pcg32, Pcg32::from_entropy()); gen_bytes!(gen_bytes_pcg64, Pcg64::from_entropy()); gen_bytes!(gen_bytes_pcg64mcg, Pcg64Mcg::from_entropy()); +gen_bytes!(gen_bytes_pcg64dxsm, Pcg64Dxsm::from_entropy()); gen_bytes!(gen_bytes_chacha8, ChaCha8Rng::from_entropy()); gen_bytes!(gen_bytes_chacha12, ChaCha12Rng::from_entropy()); gen_bytes!(gen_bytes_chacha20, ChaCha20Rng::from_entropy()); @@ -73,6 +74,7 @@ gen_uint!(gen_u32_step, u32, StepRng::new(0, 1)); gen_uint!(gen_u32_pcg32, u32, Pcg32::from_entropy()); gen_uint!(gen_u32_pcg64, u32, Pcg64::from_entropy()); gen_uint!(gen_u32_pcg64mcg, u32, Pcg64Mcg::from_entropy()); +gen_uint!(gen_u32_pcg64dxsm, u32, Pcg64Dxsm::from_entropy()); gen_uint!(gen_u32_chacha8, u32, ChaCha8Rng::from_entropy()); gen_uint!(gen_u32_chacha12, u32, ChaCha12Rng::from_entropy()); gen_uint!(gen_u32_chacha20, u32, ChaCha20Rng::from_entropy()); @@ -85,6 +87,7 @@ gen_uint!(gen_u64_step, u64, StepRng::new(0, 1)); gen_uint!(gen_u64_pcg32, u64, Pcg32::from_entropy()); gen_uint!(gen_u64_pcg64, u64, Pcg64::from_entropy()); gen_uint!(gen_u64_pcg64mcg, u64, Pcg64Mcg::from_entropy()); +gen_uint!(gen_u64_pcg64dxsm, u64, Pcg64Dxsm::from_entropy()); gen_uint!(gen_u64_chacha8, u64, ChaCha8Rng::from_entropy()); gen_uint!(gen_u64_chacha12, u64, ChaCha12Rng::from_entropy()); gen_uint!(gen_u64_chacha20, u64, ChaCha20Rng::from_entropy()); @@ -109,6 +112,7 @@ macro_rules! init_gen { init_gen!(init_pcg32, Pcg32); init_gen!(init_pcg64, Pcg64); init_gen!(init_pcg64mcg, Pcg64Mcg); +init_gen!(init_pcg64dxsm, Pcg64Dxsm); init_gen!(init_chacha, ChaCha20Rng); const RESEEDING_BYTES_LEN: usize = 1024 * 1024; From 7f9aa2b43c05a536a29f8447efd0de470e268cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Tue, 28 Dec 2021 16:31:42 +0300 Subject: [PATCH 233/443] rand_distr: fix no_std build --- rand_distr/src/binomial.rs | 2 ++ rand_distr/src/geometric.rs | 2 ++ rand_distr/src/hypergeometric.rs | 2 ++ rand_distr/src/utils.rs | 3 ++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 8e7513a2477..d1637b6d1ee 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -13,6 +13,8 @@ use crate::{Distribution, Uniform}; use rand::Rng; use core::fmt; use core::cmp::Ordering; +#[allow(unused_imports)] +use num_traits::Float; /// The binomial distribution `Binomial(n, p)`. /// diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 7988261a380..78ad6cce64b 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -3,6 +3,8 @@ use crate::Distribution; use rand::Rng; use core::fmt; +#[allow(unused_imports)] +use num_traits::Float; /// The geometric distribution `Geometric(p)` bounded to `[0, u64::MAX]`. /// diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 8ab2dca0333..9a529096242 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -4,6 +4,8 @@ use crate::Distribution; use rand::Rng; use rand::distributions::uniform::Uniform; use core::fmt; +#[allow(unused_imports)] +use num_traits::Float; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] diff --git a/rand_distr/src/utils.rs b/rand_distr/src/utils.rs index f097bb45780..4638e3623d2 100644 --- a/rand_distr/src/utils.rs +++ b/rand_distr/src/utils.rs @@ -11,6 +11,7 @@ use crate::ziggurat_tables; use rand::distributions::hidden_export::IntoFloat; use rand::Rng; +use num_traits::Float; /// Calculates ln(gamma(x)) (natural logarithm of the gamma /// function) using the Lanczos approximation. @@ -25,7 +26,7 @@ use rand::Rng; /// `Ag(z)` is an infinite series with coefficients that can be calculated /// ahead of time - we use just the first 6 terms, which is good enough /// for most purposes. -pub(crate) fn log_gamma(x: F) -> F { +pub(crate) fn log_gamma(x: F) -> F { // precalculated 6 coefficients for the first 6 terms of the series let coefficients: [F; 6] = [ F::from(76.18009172947146).unwrap(), From e9f5cfccbf48864d47828e3267736c1336c5e00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Tue, 28 Dec 2021 16:51:07 +0300 Subject: [PATCH 234/443] bump rand_distr to v0.4.3 --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 8a9638c80fb..32a5fcaf5ae 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.4.2" +version = "0.4.3" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" From 9ef737ba5b814f6ab36cebafb59ad29885d68a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9F=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Tue, 28 Dec 2021 16:52:13 +0300 Subject: [PATCH 235/443] update changelog --- rand_distr/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index b93933e9ec9..6b0cda28ba6 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.3] - 2021-12-30 +- Fix `no_std` build (#1208) + ## [0.4.2] - 2021-09-18 - New `Zeta` and `Zipf` distributions (#1136) - New `SkewNormal` distribution (#1149) From 745ace87e539c5473f5cdc9a556912b5ffaf05b2 Mon Sep 17 00:00:00 2001 From: warren Date: Sun, 2 Jan 2022 14:28:36 -0500 Subject: [PATCH 236/443] rand_distr: Fix dirichlet sample method for small alpha. Generating Dirichlet samples using the method based on samples from the gamma distribution can result in samples being nan if all the values in alpha are sufficiently small. The fix is to instead use the method based on the marginal distributions being the beta distribution (i.e. the "stick breaking" method) when all values in alpha are small. --- rand_distr/src/dirichlet.rs | 131 +++++++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 11 deletions(-) diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 0ffbc40a049..f8164496266 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -9,8 +9,8 @@ //! The dirichlet distribution. #![cfg(feature = "alloc")] -use num_traits::Float; -use crate::{Distribution, Exp1, Gamma, Open01, StandardNormal}; +use num_traits::{Float, NumCast}; +use crate::{Beta, Distribution, Exp1, Gamma, Open01, StandardNormal}; use rand::Rng; use core::fmt; use alloc::{boxed::Box, vec, vec::Vec}; @@ -123,16 +123,56 @@ where fn sample(&self, rng: &mut R) -> Vec { let n = self.alpha.len(); let mut samples = vec![F::zero(); n]; - let mut sum = F::zero(); - for (s, &a) in samples.iter_mut().zip(self.alpha.iter()) { - let g = Gamma::new(a, F::one()).unwrap(); - *s = g.sample(rng); - sum = sum + (*s); - } - let invacc = F::one() / sum; - for s in samples.iter_mut() { - *s = (*s)*invacc; + if self.alpha.iter().all(|x| *x <= NumCast::from(0.1).unwrap()) { + // All the values in alpha are less than 0.1. + // + // When all the alpha parameters are sufficiently small, there + // is a nontrivial probability that the samples from the gamma + // distributions used in the other method will all be 0, which + // results in the dirichlet samples being nan. So instead of + // use that method, use the "stick breaking" method based on the + // marginal beta distributions. + // + // Form the right-to-left cumulative sum of alpha, exluding the + // first element of alpha. E.g. if alpha = [a0, a1, a2, a3], then + // after the call to `alpha_sum_rl.reverse()` below, alpha_sum_rl + // will hold [a1+a2+a3, a2+a3, a3]. + let mut alpha_sum_rl: Vec = self + .alpha + .iter() + .skip(1) + .rev() + // scan does the cumulative sum + .scan(F::zero(), |sum, x| { + *sum = *sum + *x; + Some(*sum) + }) + .collect(); + alpha_sum_rl.reverse(); + let mut acc = F::one(); + for ((s, &a), &b) in samples + .iter_mut() + .zip(self.alpha.iter()) + .zip(alpha_sum_rl.iter()) + { + let beta = Beta::new(a, b).unwrap(); + let beta_sample = beta.sample(rng); + *s = acc * beta_sample; + acc = acc * (F::one() - beta_sample); + } + samples[n - 1] = acc; + } else { + let mut sum = F::zero(); + for (s, &a) in samples.iter_mut().zip(self.alpha.iter()) { + let g = Gamma::new(a, F::one()).unwrap(); + *s = g.sample(rng); + sum = sum + (*s); + } + let invacc = F::one() / sum; + for s in samples.iter_mut() { + *s = (*s) * invacc; + } } samples } @@ -142,6 +182,33 @@ where mod test { use super::*; + // + // Check that the means of the components of n samples from + // the Dirichlet distribution agree with the expected means + // with a relative tolerance of rtol. + // + // This is a crude statistical test, but it will catch egregious + // mistakes. It will also also fail if any samples contain nan. + // + fn check_dirichlet_means(alpha: &Vec, n: i32, rtol: f64, seed: u64) { + let d = Dirichlet::new(&alpha).unwrap(); + let alpha_len = d.alpha.len(); + let mut rng = crate::test::rng(seed); + let mut sums = vec![0.0; alpha_len]; + for _ in 0..n { + let samples = d.sample(&mut rng); + for i in 0..alpha_len { + sums[i] += samples[i]; + } + } + let sample_mean: Vec = sums.iter().map(|x| x / n as f64).collect(); + let alpha_sum: f64 = d.alpha.iter().sum(); + let expected_mean: Vec = d.alpha.iter().map(|x| x / alpha_sum).collect(); + for i in 0..alpha_len { + assert_almost_eq!(sample_mean[i], expected_mean[i], rtol); + } + } + #[test] fn test_dirichlet() { let d = Dirichlet::new(&[1.0, 2.0, 3.0]).unwrap(); @@ -172,6 +239,48 @@ mod test { .collect(); } + #[test] + fn test_dirichlet_means() { + // Check the means of 20000 samples for several different alphas. + let alpha_set = vec![ + vec![0.5, 0.25], + vec![123.0, 75.0], + vec![2.0, 2.5, 5.0, 7.0], + vec![0.1, 8.0, 1.0, 2.0, 2.0, 0.85, 0.05, 12.5], + ]; + let n = 20000; + let rtol = 2e-2; + let seed = 1317624576693539401; + for alpha in alpha_set { + check_dirichlet_means(&alpha, n, rtol, seed); + } + } + + #[test] + fn test_dirichlet_means_very_small_alpha() { + // With values of alpha that are all 0.001, check that the means of the + // components of 10000 samples are within 1% of the expected means. + // With the sampling method based on gamma variates, this test would + // fail, with about 10% of the samples containing nan. + let alpha = vec![0.001, 0.001, 0.001]; + let n = 10000; + let rtol = 1e-2; + let seed = 1317624576693539401; + check_dirichlet_means(&alpha, n, rtol, seed); + } + + #[test] + fn test_dirichlet_means_small_alpha() { + // With values of alpha that are all less than 0.1, check that the + // means of the components of 150000 samples are within 0.1% of the + // expected means. + let alpha = vec![0.05, 0.025, 0.075, 0.05]; + let n = 150000; + let rtol = 1e-3; + let seed = 1317624576693539401; + check_dirichlet_means(&alpha, n, rtol, seed); + } + #[test] #[should_panic] fn test_dirichlet_invalid_length() { From 73f8ffd16379390e624ac53cd6882dd679dd9a6f Mon Sep 17 00:00:00 2001 From: novacrazy Date: Sun, 6 Feb 2022 21:19:58 -0600 Subject: [PATCH 237/443] Remove unused `slice_partition_at_index` feature --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6f8665a2d17..6d847180111 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,6 @@ #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] #![cfg_attr(feature = "simd_support", feature(stdsimd))] -#![cfg_attr(feature = "nightly", feature(slice_partition_at_index))] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![allow( clippy::float_cmp, From d3ca11b0bcc1f42fe34ba4f90f99509b7eb4ff18 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 8 Feb 2022 09:33:33 +0000 Subject: [PATCH 238/443] Update to packed_simd_2 0.3.7 --- Cargo.toml | 2 +- src/distributions/uniform.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ef49d1876e..98ba373c68f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = fals [dependencies.packed_simd] # NOTE: so far no version works reliably due to dependence on unstable features package = "packed_simd_2" -version = "0.3.6" +version = "0.3.7" optional = true features = ["into_bits"] diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 482461f0f13..6b9e70f0839 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1430,7 +1430,7 @@ mod tests { #[test] #[should_panic] fn test_float_overflow() { - Uniform::from(::core::f64::MIN..::core::f64::MAX); + let _ = Uniform::from(::core::f64::MIN..::core::f64::MAX); } #[test] From 9f20df04d88698c38515833d6db62d7eb50d8b80 Mon Sep 17 00:00:00 2001 From: Will Springer Date: Thu, 10 Feb 2022 19:30:49 -0800 Subject: [PATCH 239/443] Making distributions comparable by deriving PartialEq. Tests included --- rand_distr/src/binomial.rs | 7 +++- rand_distr/src/cauchy.rs | 7 +++- rand_distr/src/dirichlet.rs | 7 +++- rand_distr/src/exponential.rs | 7 +++- rand_distr/src/frechet.rs | 7 +++- rand_distr/src/gamma.rs | 49 +++++++++++++++++------ rand_distr/src/geometric.rs | 7 +++- rand_distr/src/gumbel.rs | 7 +++- rand_distr/src/hypergeometric.rs | 9 ++++- rand_distr/src/inverse_gaussian.rs | 7 +++- rand_distr/src/normal.rs | 14 ++++++- rand_distr/src/normal_inverse_gaussian.rs | 7 +++- rand_distr/src/pareto.rs | 7 +++- rand_distr/src/pert.rs | 7 +++- rand_distr/src/poisson.rs | 9 ++++- rand_distr/src/skew_normal.rs | 7 +++- rand_distr/src/triangular.rs | 7 +++- rand_distr/src/weibull.rs | 7 +++- rand_distr/src/zipf.rs | 14 ++++++- src/distributions/bernoulli.rs | 7 +++- src/distributions/uniform.rs | 14 +++++-- src/distributions/weighted_index.rs | 7 +++- 22 files changed, 182 insertions(+), 39 deletions(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index d1637b6d1ee..6dbf7ab7494 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -30,7 +30,7 @@ use num_traits::Float; /// let v = bin.sample(&mut rand::thread_rng()); /// println!("{} is from a binomial distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Binomial { /// Number of trials. @@ -347,4 +347,9 @@ mod test { fn test_binomial_invalid_lambda_neg() { Binomial::new(20, -10.0).unwrap(); } + + #[test] + fn binomial_distributions_can_be_compared() { + assert_eq!(Binomial::new(1, 1.0), Binomial::new(1, 1.0)); + } } diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 8c2ccdd3f7b..9aff7e625f4 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -31,7 +31,7 @@ use core::fmt; /// let v = cau.sample(&mut rand::thread_rng()); /// println!("{} is from a Cauchy(2, 5) distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Cauchy where F: Float + FloatConst, Standard: Distribution @@ -164,4 +164,9 @@ mod test { assert_almost_eq!(*a, *b, 1e-5); } } + + #[test] + fn cauchy_distributions_can_be_compared() { + assert_eq!(Cauchy::new(1.0, 2.0), Cauchy::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 0ffbc40a049..786cbccd0cc 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -32,7 +32,7 @@ use alloc::{boxed::Box, vec, vec::Vec}; /// println!("{:?} is from a Dirichlet([1.0, 2.0, 3.0]) distribution", samples); /// ``` #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Dirichlet where @@ -183,4 +183,9 @@ mod test { fn test_dirichlet_invalid_alpha() { Dirichlet::new_with_size(0.0f64, 2).unwrap(); } + + #[test] + fn dirichlet_distributions_can_be_compared() { + assert_eq!(Dirichlet::new(&[1.0, 2.0]), Dirichlet::new(&[1.0, 2.0])); + } } diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index 4e33c3cac6e..e3d2a8d1cf6 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -91,7 +91,7 @@ impl Distribution for Exp1 { /// let v = exp.sample(&mut rand::thread_rng()); /// println!("{} is from a Exp(2) distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Exp where F: Float, Exp1: Distribution @@ -178,4 +178,9 @@ mod test { fn test_exp_invalid_lambda_nan() { Exp::new(f64::nan()).unwrap(); } + + #[test] + fn exponential_distributions_can_be_compared() { + assert_eq!(Exp::new(1.0), Exp::new(1.0)); + } } diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index 0239fe83b12..63205b40cbd 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -27,7 +27,7 @@ use rand::Rng; /// let val: f64 = thread_rng().sample(Frechet::new(0.0, 1.0, 1.0).unwrap()); /// println!("{}", val); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Frechet where @@ -182,4 +182,9 @@ mod tests { .zip(&probabilities) .all(|(p_hat, p)| (p_hat - p).abs() < 0.003)) } + + #[test] + fn frechet_distributions_can_be_compared() { + assert_eq!(Frechet::new(1.0, 2.0, 3.0), Frechet::new(1.0, 2.0, 3.0)); + } } diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 87faf11c893..debad0c8438 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -54,7 +54,7 @@ use serde::{Serialize, Deserialize}; /// Generating Gamma Variables" *ACM Trans. Math. Softw.* 26, 3 /// (September 2000), 363-372. /// DOI:[10.1145/358407.358414](https://doi.acm.org/10.1145/358407.358414) -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Gamma where @@ -91,7 +91,7 @@ impl fmt::Display for Error { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum GammaRepr where @@ -119,7 +119,7 @@ where /// /// See `Gamma` for sampling from a Gamma distribution with general /// shape parameters. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct GammaSmallShape where @@ -135,7 +135,7 @@ where /// /// See `Gamma` for sampling from a Gamma distribution with general /// shape parameters. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct GammaLargeShape where @@ -280,7 +280,7 @@ where /// let v = chi.sample(&mut rand::thread_rng()); /// println!("{} is from a χ²(11) distribution", v) /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct ChiSquared where @@ -314,7 +314,7 @@ impl fmt::Display for ChiSquaredError { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for ChiSquaredError {} -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum ChiSquaredRepr where @@ -385,7 +385,7 @@ where /// let v = f.sample(&mut rand::thread_rng()); /// println!("{} is from an F(2, 32) distribution", v) /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct FisherF where @@ -472,7 +472,7 @@ where /// let v = t.sample(&mut rand::thread_rng()); /// println!("{} is from a t(11) distribution", v) /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct StudentT where @@ -522,7 +522,7 @@ where /// Generating beta variates with nonintegral shape parameters. /// Communications of the ACM 21, 317-322. /// https://doi.org/10.1145/359460.359482 -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum BetaAlgorithm { BB(BB), @@ -530,7 +530,7 @@ enum BetaAlgorithm { } /// Algorithm BB for `min(alpha, beta) > 1`. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct BB { alpha: N, @@ -539,7 +539,7 @@ struct BB { } /// Algorithm BC for `min(alpha, beta) <= 1`. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] struct BC { alpha: N, @@ -560,7 +560,7 @@ struct BC { /// let v = beta.sample(&mut rand::thread_rng()); /// println!("{} is from a Beta(2, 5) distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Beta where @@ -811,4 +811,29 @@ mod test { assert!(!beta.sample(&mut rng).is_nan(), "failed at i={}", i); } } + + #[test] + fn gamma_distributions_can_be_compared() { + assert_eq!(Gamma::new(1.0, 2.0), Gamma::new(1.0, 2.0)); + } + + #[test] + fn beta_distributions_can_be_compared() { + assert_eq!(Beta::new(1.0, 2.0), Beta::new(1.0, 2.0)); + } + + #[test] + fn chi_squared_distributions_can_be_compared() { + assert_eq!(ChiSquared::new(1.0), ChiSquared::new(1.0)); + } + + #[test] + fn fisher_f_distributions_can_be_compared() { + assert_eq!(FisherF::new(1.0, 2.0), FisherF::new(1.0, 2.0)); + } + + #[test] + fn student_t_distributions_can_be_compared() { + assert_eq!(StudentT::new(1.0), StudentT::new(1.0)); + } } diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 78ad6cce64b..3ea8b8f3e13 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -27,7 +27,7 @@ use num_traits::Float; /// let v = geo.sample(&mut rand::thread_rng()); /// println!("{} is from a Geometric(0.25) distribution", v); /// ``` -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Geometric { @@ -235,4 +235,9 @@ mod test { results.iter().map(|x| (x - mean) * (x - mean)).sum::() / results.len() as f64; assert!((variance - expected_variance).abs() < expected_variance / 10.0); } + + #[test] + fn geometric_distributions_can_be_compared() { + assert_eq!(Geometric::new(1.0), Geometric::new(1.0)); + } } diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 2b2019457c0..b254919f3b8 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -27,7 +27,7 @@ use rand::Rng; /// let val: f64 = thread_rng().sample(Gumbel::new(0.0, 1.0).unwrap()); /// println!("{}", val); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Gumbel where @@ -152,4 +152,9 @@ mod tests { .zip(&probabilities) .all(|(p_hat, p)| (p_hat - p).abs() < 0.003)) } + + #[test] + fn gumbel_distributions_can_be_compared() { + assert_eq!(Gumbel::new(1.0, 2.0), Gumbel::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 9a529096242..4761450360d 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -7,7 +7,7 @@ use core::fmt; #[allow(unused_imports)] use num_traits::Float; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] enum SamplingMethod { InverseTransform{ initial_p: f64, initial_x: i64 }, @@ -45,7 +45,7 @@ enum SamplingMethod { /// let v = hypergeo.sample(&mut rand::thread_rng()); /// println!("{} is from a hypergeometric distribution", v); /// ``` -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Hypergeometric { n1: u64, @@ -419,4 +419,9 @@ mod test { test_hypergeometric_mean_and_variance(10100, 10000, 1000, &mut rng); test_hypergeometric_mean_and_variance(100100, 100, 10000, &mut rng); } + + #[test] + fn hypergeometric_distributions_can_be_compared() { + assert_eq!(Hypergeometric::new(1, 2, 3), Hypergeometric::new(1, 2, 3)); + } } diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 58986a769aa..ba845fd1505 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -26,7 +26,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} /// The [inverse Gaussian distribution](https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution) -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct InverseGaussian where @@ -109,4 +109,9 @@ mod tests { assert!(InverseGaussian::new(1.0, -1.0).is_err()); assert!(InverseGaussian::new(1.0, 1.0).is_ok()); } + + #[test] + fn inverse_gaussian_distributions_can_be_compared() { + assert_eq!(InverseGaussian::new(1.0, 2.0), InverseGaussian::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 7078a894f43..b3b801dfed9 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -112,7 +112,7 @@ impl Distribution for StandardNormal { /// ``` /// /// [`StandardNormal`]: crate::StandardNormal -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Normal where F: Float, StandardNormal: Distribution @@ -227,7 +227,7 @@ where F: Float, StandardNormal: Distribution /// let v = log_normal.sample(&mut rand::thread_rng()); /// println!("{} is from an ln N(2, 9) distribution", v) /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct LogNormal where F: Float, StandardNormal: Distribution @@ -368,4 +368,14 @@ mod tests { assert!(LogNormal::from_mean_cv(0.0, 1.0).is_err()); assert!(LogNormal::from_mean_cv(1.0, -1.0).is_err()); } + + #[test] + fn normal_distributions_can_be_compared() { + assert_eq!(Normal::new(1.0, 2.0), Normal::new(1.0, 2.0)); + } + + #[test] + fn log_normal_distributions_can_be_compared() { + assert_eq!(LogNormal::new(1.0, 2.0), LogNormal::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index c4d693d031d..e05d5b09ef3 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -26,7 +26,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} /// The [normal-inverse Gaussian distribution](https://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct NormalInverseGaussian where @@ -104,4 +104,9 @@ mod tests { assert!(NormalInverseGaussian::new(1.0, 2.0).is_err()); assert!(NormalInverseGaussian::new(2.0, 1.0).is_ok()); } + + #[test] + fn normal_inverse_gaussian_distributions_can_be_compared() { + assert_eq!(NormalInverseGaussian::new(1.0, 2.0), NormalInverseGaussian::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index cd61894c526..25c8e0537dd 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -23,7 +23,7 @@ use core::fmt; /// let val: f64 = thread_rng().sample(Pareto::new(1., 2.).unwrap()); /// println!("{}", val); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Pareto where F: Float, OpenClosed01: Distribution @@ -131,4 +131,9 @@ mod tests { 105.8826669383772, ]); } + + #[test] + fn pareto_distributions_can_be_compared() { + assert_eq!(Pareto::new(1.0, 2.0), Pareto::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index 4ead1fb8f74..db89fff7bfb 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -30,7 +30,7 @@ use core::fmt; /// ``` /// /// [`Triangular`]: crate::Triangular -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Pert where @@ -146,4 +146,9 @@ mod test { assert!(Pert::new(min, max, mode).is_err()); } } + + #[test] + fn pert_distributions_can_be_compared() { + assert_eq!(Pert::new(1.0, 3.0, 2.0), Pert::new(1.0, 3.0, 2.0)); + } } diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index dc355258dfe..8b9bffd020e 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -28,7 +28,7 @@ use core::fmt; /// let v = poi.sample(&mut rand::thread_rng()); /// println!("{} is from a Poisson(2) distribution", v); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Poisson where F: Float + FloatConst, Standard: Distribution @@ -178,4 +178,9 @@ mod test { fn test_poisson_invalid_lambda_neg() { Poisson::new(-10.0).unwrap(); } -} + + #[test] + fn poisson_distributions_can_be_compared() { + assert_eq!(Poisson::new(1.0), Poisson::new(1.0)); + } +} \ No newline at end of file diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 7d91d0bdc46..146b4ead125 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -40,7 +40,7 @@ use rand::Rng; /// [skew normal distribution]: https://en.wikipedia.org/wiki/Skew_normal_distribution /// [`Normal`]: struct.Normal.html /// [A Method to Simulate the Skew Normal Distribution]: https://dx.doi.org/10.4236/am.2014.513201 -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct SkewNormal where @@ -253,4 +253,9 @@ mod tests { assert!(value.is_nan()); } } + + #[test] + fn skew_normal_distributions_can_be_compared() { + assert_eq!(SkewNormal::new(1.0, 2.0, 3.0), SkewNormal::new(1.0, 2.0, 3.0)); + } } diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index ba6d36445ce..eef7d190133 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -31,7 +31,7 @@ use core::fmt; /// ``` /// /// [`Pert`]: crate::Pert -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Triangular where F: Float, Standard: Distribution @@ -130,4 +130,9 @@ mod test { assert!(Triangular::new(min, max, mode).is_err()); } } + + #[test] + fn triangular_distributions_can_be_compared() { + assert_eq!(Triangular::new(1.0, 3.0, 2.0), Triangular::new(1.0, 3.0, 2.0)); + } } diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index b390ad3ff2c..fe45eff6613 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -23,7 +23,7 @@ use core::fmt; /// let val: f64 = thread_rng().sample(Weibull::new(1., 10.).unwrap()); /// println!("{}", val); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Weibull where F: Float, OpenClosed01: Distribution @@ -129,4 +129,9 @@ mod tests { 7.877212340241561, ]); } + + #[test] + fn weibull_distributions_can_be_compared() { + assert_eq!(Weibull::new(1.0, 2.0), Weibull::new(1.0, 2.0)); + } } diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index ad322c4e1b3..84d33c052e1 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -46,7 +46,7 @@ use core::fmt; /// /// [zeta distribution]: https://en.wikipedia.org/wiki/Zeta_distribution /// [Non-Uniform Random Variate Generation]: https://doi.org/10.1007/978-1-4613-8643-8 -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Zeta where F: Float, Standard: Distribution, OpenClosed01: Distribution { @@ -142,7 +142,7 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution /// due to Jason Crease[1]. /// /// [1]: https://jasoncrease.medium.com/rejection-sampling-the-zipf-distribution-6b359792cffa -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Zipf where F: Float, Standard: Distribution { n: F, @@ -371,4 +371,14 @@ mod tests { 1.0, 2.0, 3.0, 2.0 ]); } + + #[test] + fn zipf_distributions_can_be_compared() { + assert_eq!(Zipf::new(1, 2.0), Zipf::new(1, 2.0)); + } + + #[test] + fn zeta_distributions_can_be_compared() { + assert_eq!(Zeta::new(1.0), Zeta::new(1.0)); + } } diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index bf0d5e5eeb9..226db79fa9c 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -33,7 +33,7 @@ use serde::{Serialize, Deserialize}; /// This `Bernoulli` distribution uses 64 bits from the RNG (a `u64`), /// so only probabilities that are multiples of 2-64 can be /// represented. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Bernoulli { /// Probability of success, relative to the maximal integer. @@ -211,4 +211,9 @@ mod test { true, false, false, true, false, false, true, true, true, true ]); } + + #[test] + fn bernoulli_distributions_can_be_compared() { + assert_eq!(Bernoulli::new(1.0), Bernoulli::new(1.0)); + } } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 6b9e70f0839..261357b2456 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -172,7 +172,7 @@ use serde::{Serialize, Deserialize}; /// [`new`]: Uniform::new /// [`new_inclusive`]: Uniform::new_inclusive /// [`Rng::gen_range`]: Rng::gen_range -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] #[cfg_attr(feature = "serde1", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")))] @@ -418,7 +418,7 @@ impl SampleRange for RangeInclusive { /// An alternative to using a modulus is widening multiply: After a widening /// multiply by `range`, the result is in the high word. Then comparing the low /// word against `zone` makes sure our distribution is uniform. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UniformInt { low: X, @@ -806,7 +806,7 @@ impl UniformSampler for UniformChar { /// [`new`]: UniformSampler::new /// [`new_inclusive`]: UniformSampler::new_inclusive /// [`Standard`]: crate::distributions::Standard -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UniformFloat { low: X, @@ -1647,4 +1647,12 @@ mod tests { ], ); } + + #[test] + fn uniform_distributions_can_be_compared() { + assert_eq!(Uniform::new(1.0, 2.0), Uniform::new(1.0, 2.0)); + + // To cover UniformInt + assert_eq!(Uniform::new(1 as u32, 2 as u32), Uniform::new(1 as u32, 2 as u32)); + } } diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 32da37f6cd3..8252b172f7f 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -75,7 +75,7 @@ use serde::{Serialize, Deserialize}; /// /// [`Uniform`]: crate::distributions::Uniform /// [`RngCore`]: crate::RngCore -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub struct WeightedIndex { @@ -418,6 +418,11 @@ mod test { 2, 2, 1, 3, 2, 1, 3, 3, 2, 1, ]); } + + #[test] + fn weighted_index_distributions_can_be_compared() { + assert_eq!(WeightedIndex::new(&[1, 2]), WeightedIndex::new(&[1, 2])); + } } /// Error type returned from `WeightedIndex::new`. From 937320cbfeebd4352a23086d9c6e68f067f74644 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 14 Feb 2022 08:37:01 +0000 Subject: [PATCH 240/443] Update CHANGELOG for 0.8.5 (#1221) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c91fe96d3d5..b0872af6d39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Fix "min_const_gen" feature for `no_std` (#1173) - Check `libc::pthread_atfork` return value with panic on error (#1178) - More robust reseeding in case `ReseedingRng` is used from a fork handler (#1178) +- Fix nightly: remove unused `slice_partition_at_index` feature (#1215) +- Fix nightly + `simd_support`: update `packed_simd` (#1216) ### Rngs - `StdRng`: Switch from HC128 to ChaCha12 on emscripten (#1142). From 546acdc85101b8fa4fb0a14cda683332044700ff Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 14 Feb 2022 08:38:55 +0000 Subject: [PATCH 241/443] README: bump rand version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44c2e4d518e..d276dfb2141 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -rand = "0.8.4" +rand = "0.8.5" ``` To get started using Rand, see [The Book](https://rust-random.github.io/book). From a8474f7932e2f0b9d8ee2b009f946049fecc317c Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 9 Mar 2022 02:34:13 -0500 Subject: [PATCH 242/443] update Miri CI config (#1223) --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e90e380645..fa1ff740b2c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -150,9 +150,9 @@ jobs: - uses: actions/checkout@v2 - name: Install toolchain run: | - MIRI_NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri) - rustup default "$MIRI_NIGHTLY" - rustup component add miri + rustup toolchain install nightly --component miri + rustup override set nightly + cargo miri setup - name: Test rand run: | cargo miri test --no-default-features --lib --tests From 5f0d3a10a9a28cec7831bedd2f5225e2e35a201c Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 15 Sep 2021 19:12:24 +0200 Subject: [PATCH 243/443] rand_distr: Remove unused fields This breaks serialization compatibility with older versions. --- rand_distr/CHANGELOG.md | 4 ++++ rand_distr/src/gamma.rs | 3 +-- rand_distr/src/normal_inverse_gaussian.rs | 2 -- rand_distr/src/zipf.rs | 3 +-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 6b0cda28ba6..66389d670bf 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0] - unreleased +- Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) + This breaks serialization compatibility with older versions. + ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index debad0c8438..1a575bd6a9f 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -544,7 +544,6 @@ struct BB { struct BC { alpha: N, beta: N, - delta: N, kappa1: N, kappa2: N, } @@ -646,7 +645,7 @@ where Ok(Beta { a, b, switched_params, algorithm: BetaAlgorithm::BC(BC { - alpha, beta, delta, kappa1, kappa2, + alpha, beta, kappa1, kappa2, }) }) } diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index e05d5b09ef3..b1ba588ac8d 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -34,7 +34,6 @@ where StandardNormal: Distribution, Standard: Distribution, { - alpha: F, beta: F, inverse_gaussian: InverseGaussian, } @@ -63,7 +62,6 @@ where let inverse_gaussian = InverseGaussian::new(mu, F::one()).unwrap(); Ok(Self { - alpha, beta, inverse_gaussian, }) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 84d33c052e1..e15b6cdd197 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -145,7 +145,6 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution #[derive(Clone, Copy, Debug, PartialEq)] pub struct Zipf where F: Float, Standard: Distribution { - n: F, s: F, t: F, q: F, @@ -202,7 +201,7 @@ where F: Float, Standard: Distribution { }; debug_assert!(t > F::zero()); Ok(Zipf { - n, s, t, q + s, t, q }) } From 13193fcb8bf540ce417f5c8a39b616ca0210f4b6 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Wed, 20 Apr 2022 10:03:17 +0800 Subject: [PATCH 244/443] fix typo Signed-off-by: cuishuang --- rand_distr/tests/value_stability.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index 65c49644a41..d3754705db5 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -64,7 +64,7 @@ fn test_samples>( } #[test] -fn binominal_stability() { +fn binomial_stability() { // We have multiple code paths: np < 10, p > 0.5 test_samples(353, Binomial::new(2, 0.7).unwrap(), &[1, 1, 2, 1]); test_samples(353, Binomial::new(20, 0.3).unwrap(), &[7, 7, 5, 7]); From 3543f4b0258ecec04be570bbe9dc6e50d80bd3c1 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Thu, 19 May 2022 10:55:31 -0600 Subject: [PATCH 245/443] Make `CryptoRngCore` trait imply `CryptoRng` as well (#1230) --- rand_core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index fdbf6675b96..1234a566c05 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -226,7 +226,7 @@ pub trait CryptoRng {} /// buf /// } /// ``` -pub trait CryptoRngCore: RngCore { +pub trait CryptoRngCore: CryptoRng + RngCore { /// Upcast to an [`RngCore`] trait object. fn as_rngcore(&mut self) -> &mut dyn RngCore; } From 330efe9d42e26bc6a01dc7b6f60602f73e976035 Mon Sep 17 00:00:00 2001 From: masonk Date: Thu, 7 Jul 2022 03:20:11 -0400 Subject: [PATCH 246/443] Deterministic Rayon monte carlo example (#1236) * Deterministic Rayon monte carlo * Update deterministic mt with a batching example * discuss determinism in the context of rayon + rand * reword the discussion Co-authored-by: Mason Kramer --- Cargo.toml | 1 + examples/rayon-monte-carlo.rs | 79 +++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100755 examples/rayon-monte-carlo.rs diff --git a/Cargo.toml b/Cargo.toml index 98ba373c68f..b013057427b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,3 +83,4 @@ libc = { version = "0.2.22", optional = true, default-features = false } rand_pcg = { path = "rand_pcg", version = "0.3.0" } # Only to test serde1 bincode = "1.2.1" +rayon = "1.5.3" diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs new file mode 100755 index 00000000000..041f7379c48 --- /dev/null +++ b/examples/rayon-monte-carlo.rs @@ -0,0 +1,79 @@ +// Copyright 2018 Developers of the Rand project. +// Copyright 2013-2018 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! # Monte Carlo estimation of π with a chosen seed and rayon for parallelism +//! +//! Imagine that we have a square with sides of length 2 and a unit circle +//! (radius = 1), both centered at the origin. The areas are: +//! +//! ```text +//! area of circle = πr² = π * r * r = π +//! area of square = 2² = 4 +//! ``` +//! +//! The circle is entirely within the square, so if we sample many points +//! randomly from the square, roughly π / 4 of them should be inside the circle. +//! +//! We can use the above fact to estimate the value of π: pick many points in +//! the square at random, calculate the fraction that fall within the circle, +//! and multiply this fraction by 4. +//! +//! Note on determinism: +//! It's slightly tricky to build a parallel simulation using Rayon +//! which is both efficient *and* reproducible. +//! +//! Rayon's ParallelIterator api does not guarantee that the work will be +//! batched into identical batches on every run, so we can't simply use +//! map_init to construct one RNG per Rayon batch. +//! +//! Instead, we do our own batching, so that a Rayon work item becomes a +//! batch. Then we can fix our rng stream to the batched work item. +//! Batching amortizes the cost of constructing the Rng from a fixed seed +//! over BATCH_SIZE trials. Manually batching also turns out to be faster +//! for the nondeterministic version of this program as well. + +#![cfg(all(feature = "std", feature = "std_rng"))] + +use rand::distributions::{Distribution, Uniform}; +use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; +use rayon::prelude::*; + +static SEED: u64 = 0; +static BATCH_SIZE: u64 = 10_000; +static BATCHES: u64 = 1000; + +fn main() { + let range = Uniform::new(-1.0f64, 1.0); + + let in_circle = (0..BATCHES) + .into_par_iter() + .map(|i| { + let mut rng = ChaCha8Rng::seed_from_u64(SEED); + // We chose ChaCha because it's fast, has suitable statical properties for simulation, + // and because it supports this set_stream() api, which lets us chose a different stream + // per work item. ChaCha supports 2^64 independent streams. + rng.set_stream(i); + let mut count = 0; + for _ in 0..BATCH_SIZE { + let a = range.sample(&mut rng); + let b = range.sample(&mut rng); + if a * a + b * b <= 1.0 { + count += 1; + } + } + count + }) + .reduce(|| 0usize, |a, b| a + b); + + // prints something close to 3.14159... + println!( + "π is approximately {}", + 4. * (in_circle as f64) / ((BATCH_SIZE * BATCHES) as f64) + ); +} From 6112c843654ac18812e3ff4f52a7235c4a664bf1 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 7 Jul 2022 18:58:36 +0000 Subject: [PATCH 247/443] small deterministic example update --- examples/rayon-monte-carlo.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index 041f7379c48..9b47896aaa0 100755 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -55,7 +55,7 @@ fn main() { .into_par_iter() .map(|i| { let mut rng = ChaCha8Rng::seed_from_u64(SEED); - // We chose ChaCha because it's fast, has suitable statical properties for simulation, + // We chose ChaCha because it's fast, has suitable statistical properties for simulation, // and because it supports this set_stream() api, which lets us chose a different stream // per work item. ChaCha supports 2^64 independent streams. rng.set_stream(i); @@ -69,7 +69,7 @@ fn main() { } count }) - .reduce(|| 0usize, |a, b| a + b); + .sum::(); // prints something close to 3.14159... println!( From 1f99bb72dcfebe7a8fc190bb344fff01092b1384 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Fri, 8 Jul 2022 19:36:21 +0000 Subject: [PATCH 248/443] assert deterministic --- examples/rayon-monte-carlo.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index 9b47896aaa0..82d5a36ce62 100755 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -71,6 +71,9 @@ fn main() { }) .sum::(); + // assert this is deterministic + assert_eq!(in_circle, 7852263); + // prints something close to 3.14159... println!( "π is approximately {}", From 89d7e4e184f920d2e34a133318e067f42a9eaff8 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Sat, 9 Jul 2022 03:26:38 +0000 Subject: [PATCH 249/443] another typo --- examples/rayon-monte-carlo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index 82d5a36ce62..f0e7114a657 100755 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -56,7 +56,7 @@ fn main() { .map(|i| { let mut rng = ChaCha8Rng::seed_from_u64(SEED); // We chose ChaCha because it's fast, has suitable statistical properties for simulation, - // and because it supports this set_stream() api, which lets us chose a different stream + // and because it supports this set_stream() api, which lets us choose a different stream // per work item. ChaCha supports 2^64 independent streams. rng.set_stream(i); let mut count = 0; From 599d7f8f6dbead4c9f6dcf6705c38b68b5c45eff Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Wed, 6 Jul 2022 23:44:05 -0700 Subject: [PATCH 250/443] switch to std::simd, expand SIMD stuff & docs move __m128i to stable, expand documentation, add SIMD to Bernoulli, add maskNxM, add __m512i genericize simd uniform int remove some debug stuff remove bernoulli foo foo --- Cargo.toml | 11 +-- src/distributions/bernoulli.rs | 5 +- src/distributions/float.rs | 74 +++++++++--------- src/distributions/integer.rs | 104 +++++++++++++------------ src/distributions/mod.rs | 15 +++- src/distributions/other.rs | 33 ++++++++ src/distributions/uniform.rs | 134 ++++++++++++++------------------- src/distributions/utils.rs | 133 ++++++++++++++++++-------------- src/lib.rs | 2 +- 9 files changed, 281 insertions(+), 230 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98ba373c68f..32c92fe9cdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,8 +41,8 @@ alloc = ["rand_core/alloc"] # Option: use getrandom package for seeding getrandom = ["rand_core/getrandom"] -# Option (requires nightly): experimental SIMD support -simd_support = ["packed_simd"] +# Option (requires nightly Rust): experimental SIMD support +simd_support = [] # Option (enabled by default): enable StdRng std_rng = ["rand_chacha"] @@ -68,13 +68,6 @@ log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = false, optional = true } -[dependencies.packed_simd] -# NOTE: so far no version works reliably due to dependence on unstable features -package = "packed_simd_2" -version = "0.3.7" -optional = true -features = ["into_bits"] - [target.'cfg(unix)'.dependencies] # Used for fork protection (reseeding.rs) libc = { version = "0.2.22", optional = true, default-features = false } diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 226db79fa9c..676b79a5c10 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -14,6 +14,7 @@ use core::{fmt, u64}; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; + /// The Bernoulli distribution. /// /// This is a special case of the Binomial distribution where `n = 1`. @@ -147,10 +148,10 @@ mod test { use crate::Rng; #[test] - #[cfg(feature="serde1")] + #[cfg(feature = "serde1")] fn test_serializing_deserializing_bernoulli() { let coin_flip = Bernoulli::new(0.5).unwrap(); - let de_coin_flip : Bernoulli = bincode::deserialize(&bincode::serialize(&coin_flip).unwrap()).unwrap(); + let de_coin_flip: Bernoulli = bincode::deserialize(&bincode::serialize(&coin_flip).unwrap()).unwrap(); assert_eq!(coin_flip.p_int, de_coin_flip.p_int); } diff --git a/src/distributions/float.rs b/src/distributions/float.rs index ce5946f7f01..54aebad4dc5 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -8,11 +8,11 @@ //! Basic floating-point number distributions -use crate::distributions::utils::FloatSIMDUtils; +use crate::distributions::utils::{IntAsSIMD, FloatAsSIMD, FloatSIMDUtils}; use crate::distributions::{Distribution, Standard}; use crate::Rng; use core::mem; -#[cfg(feature = "simd_support")] use packed_simd::*; +#[cfg(feature = "simd_support")] use core::simd::*; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; @@ -99,7 +99,7 @@ macro_rules! float_impls { // The exponent is encoded using an offset-binary representation let exponent_bits: $u_scalar = (($exponent_bias + exponent) as $u_scalar) << $fraction_bits; - $ty::from_bits(self | exponent_bits) + $ty::from_bits(self | $uty::splat(exponent_bits)) } } @@ -108,13 +108,13 @@ macro_rules! float_impls { // Multiply-based method; 24/53 random bits; [0, 1) interval. // We use the most significant bits because for simple RNGs // those are usually more random. - let float_size = mem::size_of::<$f_scalar>() as u32 * 8; + let float_size = mem::size_of::<$f_scalar>() as $u_scalar * 8; let precision = $fraction_bits + 1; let scale = 1.0 / ((1 as $u_scalar << precision) as $f_scalar); let value: $uty = rng.gen(); - let value = value >> (float_size - precision); - scale * $ty::cast_from_int(value) + let value = value >> $uty::splat(float_size - precision); + $ty::splat(scale) * $ty::cast_from_int(value) } } @@ -123,14 +123,14 @@ macro_rules! float_impls { // Multiply-based method; 24/53 random bits; (0, 1] interval. // We use the most significant bits because for simple RNGs // those are usually more random. - let float_size = mem::size_of::<$f_scalar>() as u32 * 8; + let float_size = mem::size_of::<$f_scalar>() as $u_scalar * 8; let precision = $fraction_bits + 1; let scale = 1.0 / ((1 as $u_scalar << precision) as $f_scalar); let value: $uty = rng.gen(); - let value = value >> (float_size - precision); + let value = value >> $uty::splat(float_size - precision); // Add 1 to shift up; will not overflow because of right-shift: - scale * $ty::cast_from_int(value + 1) + $ty::splat(scale) * $ty::cast_from_int(value + $uty::splat(1)) } } @@ -140,11 +140,11 @@ macro_rules! float_impls { // We use the most significant bits because for simple RNGs // those are usually more random. use core::$f_scalar::EPSILON; - let float_size = mem::size_of::<$f_scalar>() as u32 * 8; + let float_size = mem::size_of::<$f_scalar>() as $u_scalar * 8; let value: $uty = rng.gen(); - let fraction = value >> (float_size - $fraction_bits); - fraction.into_float_with_exponent(0) - (1.0 - EPSILON / 2.0) + let fraction = value >> $uty::splat(float_size - $fraction_bits); + fraction.into_float_with_exponent(0) - $ty::splat(1.0 - EPSILON / 2.0) } } } @@ -169,10 +169,10 @@ float_impls! { f64x4, u64x4, f64, u64, 52, 1023 } #[cfg(feature = "simd_support")] float_impls! { f64x8, u64x8, f64, u64, 52, 1023 } - #[cfg(test)] mod tests { use super::*; + use crate::distributions::utils::FloatAsSIMD; use crate::rngs::mock::StepRng; const EPSILON32: f32 = ::core::f32::EPSILON; @@ -182,29 +182,31 @@ mod tests { ($fnn:ident, $ty:ident, $ZERO:expr, $EPSILON:expr) => { #[test] fn $fnn() { + let two = $ty::splat(2.0); + // Standard let mut zeros = StepRng::new(0, 0); assert_eq!(zeros.gen::<$ty>(), $ZERO); let mut one = StepRng::new(1 << 8 | 1 << (8 + 32), 0); - assert_eq!(one.gen::<$ty>(), $EPSILON / 2.0); + assert_eq!(one.gen::<$ty>(), $EPSILON / two); let mut max = StepRng::new(!0, 0); - assert_eq!(max.gen::<$ty>(), 1.0 - $EPSILON / 2.0); + assert_eq!(max.gen::<$ty>(), $ty::splat(1.0) - $EPSILON / two); // OpenClosed01 let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.sample::<$ty, _>(OpenClosed01), 0.0 + $EPSILON / 2.0); + assert_eq!(zeros.sample::<$ty, _>(OpenClosed01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 8 | 1 << (8 + 32), 0); assert_eq!(one.sample::<$ty, _>(OpenClosed01), $EPSILON); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(OpenClosed01), $ZERO + 1.0); + assert_eq!(max.sample::<$ty, _>(OpenClosed01), $ZERO + $ty::splat(1.0)); // Open01 let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.sample::<$ty, _>(Open01), 0.0 + $EPSILON / 2.0); + assert_eq!(zeros.sample::<$ty, _>(Open01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 9 | 1 << (9 + 32), 0); - assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / 2.0 * 3.0); + assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / two * $ty::splat(3.0)); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(Open01), 1.0 - $EPSILON / 2.0); + assert_eq!(max.sample::<$ty, _>(Open01), $ty::splat(1.0) - $EPSILON / two); } }; } @@ -222,29 +224,31 @@ mod tests { ($fnn:ident, $ty:ident, $ZERO:expr, $EPSILON:expr) => { #[test] fn $fnn() { + let two = $ty::splat(2.0); + // Standard let mut zeros = StepRng::new(0, 0); assert_eq!(zeros.gen::<$ty>(), $ZERO); let mut one = StepRng::new(1 << 11, 0); - assert_eq!(one.gen::<$ty>(), $EPSILON / 2.0); + assert_eq!(one.gen::<$ty>(), $EPSILON / two); let mut max = StepRng::new(!0, 0); - assert_eq!(max.gen::<$ty>(), 1.0 - $EPSILON / 2.0); + assert_eq!(max.gen::<$ty>(), $ty::splat(1.0) - $EPSILON / two); // OpenClosed01 let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.sample::<$ty, _>(OpenClosed01), 0.0 + $EPSILON / 2.0); + assert_eq!(zeros.sample::<$ty, _>(OpenClosed01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 11, 0); assert_eq!(one.sample::<$ty, _>(OpenClosed01), $EPSILON); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(OpenClosed01), $ZERO + 1.0); + assert_eq!(max.sample::<$ty, _>(OpenClosed01), $ZERO + $ty::splat(1.0)); // Open01 let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.sample::<$ty, _>(Open01), 0.0 + $EPSILON / 2.0); + assert_eq!(zeros.sample::<$ty, _>(Open01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 12, 0); - assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / 2.0 * 3.0); + assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / two * $ty::splat(3.0)); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(Open01), 1.0 - $EPSILON / 2.0); + assert_eq!(max.sample::<$ty, _>(Open01), $ty::splat(1.0) - $EPSILON / two); } }; } @@ -296,16 +300,16 @@ mod tests { // non-SIMD types; we assume this pattern continues across all // SIMD types. - test_samples(&Standard, f32x2::new(0.0, 0.0), &[ - f32x2::new(0.0035963655, 0.7346052), - f32x2::new(0.09778172, 0.20298547), - f32x2::new(0.34296435, 0.81664366), + test_samples(&Standard, f32x2::from([0.0, 0.0]), &[ + f32x2::from([0.0035963655, 0.7346052]), + f32x2::from([0.09778172, 0.20298547]), + f32x2::from([0.34296435, 0.81664366]), ]); - test_samples(&Standard, f64x2::new(0.0, 0.0), &[ - f64x2::new(0.7346051961657583, 0.20298547462974248), - f64x2::new(0.8166436635290655, 0.7423708925400552), - f64x2::new(0.16387782224016323, 0.9087068770169618), + test_samples(&Standard, f64x2::from([0.0, 0.0]), &[ + f64x2::from([0.7346051961657583, 0.20298547462974248]), + f64x2::from([0.8166436635290655, 0.7423708925400552]), + f64x2::from([0.16387782224016323, 0.9087068770169618]), ]); } } diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index 19ce71599cb..d905044ed1e 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -11,12 +11,17 @@ use crate::distributions::{Distribution, Standard}; use crate::Rng; #[cfg(all(target_arch = "x86", feature = "simd_support"))] +use core::arch::x86::__m512i; +#[cfg(target_arch = "x86")] use core::arch::x86::{__m128i, __m256i}; #[cfg(all(target_arch = "x86_64", feature = "simd_support"))] +use core::arch::x86_64::__m512i; +#[cfg(target_arch = "x86_64")] use core::arch::x86_64::{__m128i, __m256i}; use core::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, NonZeroU128}; -#[cfg(feature = "simd_support")] use packed_simd::*; +#[cfg(feature = "simd_support")] use core::simd::*; +use core::mem; impl Distribution for Standard { #[inline] @@ -109,53 +114,63 @@ impl_nzint!(NonZeroU64, NonZeroU64::new); impl_nzint!(NonZeroU128, NonZeroU128::new); impl_nzint!(NonZeroUsize, NonZeroUsize::new); -#[cfg(feature = "simd_support")] -macro_rules! simd_impl { - ($(($intrinsic:ident, $vec:ty),)+) => {$( +macro_rules! intrinsic_impl { + ($($intrinsic:ident),+) => {$( + /// Available only on x86/64 platforms impl Distribution<$intrinsic> for Standard { #[inline] fn sample(&self, rng: &mut R) -> $intrinsic { - $intrinsic::from_bits(rng.gen::<$vec>()) + // On proper hardware, this should compile to SIMD instructions + // Verified on x86 Haswell with __m128i, __m256i + let mut buf = [0_u8; mem::size_of::<$intrinsic>()]; + rng.fill_bytes(&mut buf); + // x86 is little endian so no need for conversion + // SAFETY: we know [u8; N] and $intrinsic have the same size + unsafe { mem::transmute_copy(&buf) } } } )+}; +} - ($bits:expr,) => {}; - ($bits:expr, $ty:ty, $($ty_more:ty,)*) => { - simd_impl!($bits, $($ty_more,)*); - +#[cfg(feature = "simd_support")] +macro_rules! simd_impl { + ($($ty:ty),+) => {$( + /// Requires nightly Rust and the [`simd_support`] feature + /// + /// [`simd_support`]: https://github.com/rust-random/rand#crate-features impl Distribution<$ty> for Standard { #[inline] fn sample(&self, rng: &mut R) -> $ty { - let mut vec: $ty = Default::default(); - unsafe { - let ptr = &mut vec; - let b_ptr = &mut *(ptr as *mut $ty as *mut [u8; $bits/8]); - rng.fill_bytes(b_ptr); + // TODO: impl this generically once const generics are robust enough + let mut vec: Simd() }> = Default::default(); + rng.fill_bytes(vec.as_mut_array()); + // NOTE: replace with `to_le` if added to core::simd + #[cfg(not(target_endian = "little"))] + { + vec = vec.reverse(); } - vec.to_le() + // SAFETY: we know u8xN and $ty have the same size + unsafe { mem::transmute_copy(&vec) } } } - }; + )+}; } #[cfg(feature = "simd_support")] -simd_impl!(16, u8x2, i8x2,); -#[cfg(feature = "simd_support")] -simd_impl!(32, u8x4, i8x4, u16x2, i16x2,); -#[cfg(feature = "simd_support")] -simd_impl!(64, u8x8, i8x8, u16x4, i16x4, u32x2, i32x2,); -#[cfg(feature = "simd_support")] -simd_impl!(128, u8x16, i8x16, u16x8, i16x8, u32x4, i32x4, u64x2, i64x2,); -#[cfg(feature = "simd_support")] -simd_impl!(256, u8x32, i8x32, u16x16, i16x16, u32x8, i32x8, u64x4, i64x4,); -#[cfg(feature = "simd_support")] -simd_impl!(512, u8x64, i8x64, u16x32, i16x32, u32x16, i32x16, u64x8, i64x8,); +simd_impl!( + i8x4, i8x8, i8x16, i8x32, i8x64, i16x2, i16x4, i16x8, i16x16, i16x32, i32x2, i32x4, i32x8, + i32x16, i64x2, i64x4, i64x8, isizex2, isizex4, isizex8, u8x4, u8x8, u8x16, u8x32, u8x64, u16x2, + u16x4, u16x8, u16x16, u16x32, u32x2, u32x4, u32x8, u32x16, u64x2, u64x4, u64x8, usizex2, + usizex4, usizex8 +); + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +intrinsic_impl!(__m128i, __m256i); #[cfg(all( - feature = "simd_support", - any(target_arch = "x86", target_arch = "x86_64") + any(target_arch = "x86", target_arch = "x86_64"), + feature = "simd_support" ))] -simd_impl!((__m128i, u8x16), (__m256i, u8x32),); +intrinsic_impl!(__m512i); #[cfg(test)] mod tests { @@ -221,24 +236,19 @@ mod tests { { // We only test a sub-set of types here and make assumptions about the rest. - test_samples(u8x2::default(), &[ - u8x2::new(9, 126), - u8x2::new(247, 167), - u8x2::new(111, 149), - ]); test_samples(u8x4::default(), &[ - u8x4::new(9, 126, 87, 132), - u8x4::new(247, 167, 123, 153), - u8x4::new(111, 149, 73, 120), + u8x4::from([9, 126, 87, 132]), + u8x4::from([247, 167, 123, 153]), + u8x4::from([111, 149, 73, 120]), ]); test_samples(u8x8::default(), &[ - u8x8::new(9, 126, 87, 132, 247, 167, 123, 153), - u8x8::new(111, 149, 73, 120, 68, 171, 98, 223), - u8x8::new(24, 121, 1, 50, 13, 46, 164, 20), + u8x8::from([9, 126, 87, 132, 247, 167, 123, 153]), + u8x8::from([111, 149, 73, 120, 68, 171, 98, 223]), + u8x8::from([24, 121, 1, 50, 13, 46, 164, 20]), ]); test_samples(i64x8::default(), &[ - i64x8::new( + i64x8::from([ -7387126082252079607, -2350127744969763473, 1487364411147516184, @@ -247,8 +257,8 @@ mod tests { 6022086574635100741, -5080089175222015595, -4066367846667249123, - ), - i64x8::new( + ]), + i64x8::from([ 9180885022207963908, 3095981199532211089, 6586075293021332726, @@ -257,8 +267,8 @@ mod tests { 5287129228749947252, 444726432079249540, -1587028029513790706, - ), - i64x8::new( + ]), + i64x8::from([ 6075236523189346388, 1351763722368165432, -6192309979959753740, @@ -267,7 +277,7 @@ mod tests { 7522501477800909500, -1837258847956201231, -586926753024886735, - ), + ]), ]); } } diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 05ca80606b0..9b0a8867000 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -149,8 +149,14 @@ use crate::Rng; /// * `bool`: Generates `false` or `true`, each with probability 0.5. /// * Floating point types (`f32` and `f64`): Uniformly distributed in the /// half-open range `[0, 1)`. See notes below. -/// * Wrapping integers (`Wrapping`), besides the type identical to their +/// * Wrapping integers ([`Wrapping`]), besides the type identical to their /// normal integer variants. +/// * Non-zero integers ([`NonZeroU8`]), which are like their normal integer +/// variants but cannot produce zero. +/// * SIMD types like x86's [`__m128i`], `std::simd`'s [`u32x4`]/[`f32x4`]/ +/// [`mask32x4`] (requires [`simd_support`]), where each lane is distributed +/// like their scalar `Standard` variants. See the list of `Standard` +/// implementations for more. /// /// The `Standard` distribution also supports generation of the following /// compound types where all component types are supported: @@ -213,6 +219,13 @@ use crate::Rng; /// CPUs all methods have approximately equal performance). /// /// [`Uniform`]: uniform::Uniform +/// [`Wrapping`]: std::num::Wrapping +/// [`NonZeroU8`]: std::num::NonZeroU8 +/// [`__m128i`]: https://doc.rust-lang.org/nightly/core/arch/x86/struct.__m128i.html +/// [`u32x4`]: std::simd::u32x4 +/// [`f32x4`]: std::simd::f32x4 +/// [`mask32x4`]: std::simd::mask32x4 +/// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Standard; diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 03802a76d5f..b3b5e4e0201 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -22,6 +22,8 @@ use crate::Rng; use serde::{Serialize, Deserialize}; #[cfg(feature = "min_const_gen")] use core::mem::{self, MaybeUninit}; +#[cfg(feature = "simd_support")] +use core::simd::{Mask, Simd, LaneCount, SupportedLaneCount, MaskElement, SimdElement}; // ----- Sampling distributions ----- @@ -145,6 +147,37 @@ impl Distribution for Standard { } } +/// Requires nightly Rust and the [`simd_support`] feature +/// +/// Note that on some hardware like x86/64 mask operations like [`_mm_blendv_epi8`] +/// only care about a single bit. This means that you could use uniform random bits +/// directly: +/// +/// ```ignore +/// // this may be faster... +/// let x = unsafe { _mm_blendv_epi8(a.into(), b.into(), rng.gen::<__m128i>()) }; +/// +/// // ...than this +/// let x = rng.gen::().select(b, a); +/// ``` +/// +/// Since most bits are unused you could also generate only as many bits as you need. +/// +/// [`_mm_blendv_epi8`]: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8&ig_expand=514/ +/// [`simd_support`]: https://github.com/rust-random/rand#crate-features +#[cfg(feature = "simd_support")] +impl Distribution> for Standard +where + T: MaskElement + PartialOrd + SimdElement + Default, + LaneCount: SupportedLaneCount, + Standard: Distribution>, +{ + #[inline] + fn sample(&self, rng: &mut R) -> Mask { + rng.gen().lanes_lt(Simd::default()) + } +} + macro_rules! tuple_impl { // use variables to indicate the arity of the tuple ($($tyvar:ident),* ) => { diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 261357b2456..d9197c68045 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -110,15 +110,17 @@ use core::time::Duration; use core::ops::{Range, RangeInclusive}; use crate::distributions::float::IntoFloat; -use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, WideningMultiply}; +use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD, WideningMultiply}; use crate::distributions::Distribution; +#[cfg(feature = "simd_support")] +use crate::distributions::Standard; use crate::{Rng, RngCore}; #[cfg(not(feature = "std"))] #[allow(unused_imports)] // rustc doesn't detect that this is actually used use crate::distributions::utils::Float; -#[cfg(feature = "simd_support")] use packed_simd::*; +#[cfg(feature = "simd_support")] use core::simd::*; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; @@ -571,21 +573,30 @@ uniform_int_impl! { u128, u128, u128 } #[cfg(feature = "simd_support")] macro_rules! uniform_simd_int_impl { - ($ty:ident, $unsigned:ident, $u_scalar:ident) => { + ($ty:ident, $unsigned:ident) => { // The "pick the largest zone that can fit in an `u32`" optimization // is less useful here. Multiple lanes complicate things, we don't // know the PRNG's minimal output size, and casting to a larger vector // is generally a bad idea for SIMD performance. The user can still // implement it manually. - - // TODO: look into `Uniform::::new(0u32, 100)` functionality - // perhaps `impl SampleUniform for $u_scalar`? - impl SampleUniform for $ty { - type Sampler = UniformInt<$ty>; + impl SampleUniform for Simd<$ty, LANES> + where + LaneCount: SupportedLaneCount, + Simd<$unsigned, LANES>: + WideningMultiply, Simd<$unsigned, LANES>)>, + Standard: Distribution>, + { + type Sampler = UniformInt>; } - impl UniformSampler for UniformInt<$ty> { - type X = $ty; + impl UniformSampler for UniformInt> + where + LaneCount: SupportedLaneCount, + Simd<$unsigned, LANES>: + WideningMultiply, Simd<$unsigned, LANES>)>, + Standard: Distribution>, + { + type X = Simd<$ty, LANES>; #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. @@ -595,8 +606,8 @@ macro_rules! uniform_simd_int_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.lt(high).all(), "Uniform::new called with `low >= high`"); - UniformSampler::new_inclusive(low, high - 1) + assert!(low.lanes_lt(high).all(), "Uniform::new called with `low >= high`"); + UniformSampler::new_inclusive(low, high - Simd::splat(1)) } #[inline] // if the range is constant, this helps LLVM to do the @@ -607,20 +618,20 @@ macro_rules! uniform_simd_int_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.le(high).all(), + assert!(low.lanes_le(high).all(), "Uniform::new_inclusive called with `low > high`"); - let unsigned_max = ::core::$u_scalar::MAX; + let unsigned_max = Simd::splat(::core::$unsigned::MAX); - // NOTE: these may need to be replaced with explicitly - // wrapping operations if `packed_simd` changes - let range: $unsigned = ((high - low) + 1).cast(); + // NOTE: all `Simd` operations are inherently wrapping, + // see https://doc.rust-lang.org/std/simd/struct.Simd.html + let range: Simd<$unsigned, LANES> = ((high - low) + Simd::splat(1)).cast(); // `% 0` will panic at runtime. - let not_full_range = range.gt($unsigned::splat(0)); + let not_full_range = range.lanes_gt(Simd::splat(0)); // replacing 0 with `unsigned_max` allows a faster `select` // with bitwise OR - let modulo = not_full_range.select(range, $unsigned::splat(unsigned_max)); + let modulo = not_full_range.select(range, unsigned_max); // wrapping addition - let ints_to_reject = (unsigned_max - range + 1) % modulo; + let ints_to_reject = (unsigned_max - range + Simd::splat(1)) % modulo; // When `range` is 0, `lo` of `v.wmul(range)` will always be // zero which means only one sample is needed. let zone = unsigned_max - ints_to_reject; @@ -634,8 +645,8 @@ macro_rules! uniform_simd_int_impl { } fn sample(&self, rng: &mut R) -> Self::X { - let range: $unsigned = self.range.cast(); - let zone: $unsigned = self.z.cast(); + let range: Simd<$unsigned, LANES> = self.range.cast(); + let zone: Simd<$unsigned, LANES> = self.z.cast(); // This might seem very slow, generating a whole new // SIMD vector for every sample rejection. For most uses @@ -646,19 +657,19 @@ macro_rules! uniform_simd_int_impl { // rejection. The replacement method does however add a little // overhead. Benchmarking or calculating probabilities might // reveal contexts where this replacement method is slower. - let mut v: $unsigned = rng.gen(); + let mut v: Simd<$unsigned, LANES> = rng.gen(); loop { let (hi, lo) = v.wmul(range); - let mask = lo.le(zone); + let mask = lo.lanes_le(zone); if mask.all() { - let hi: $ty = hi.cast(); + let hi: Simd<$ty, LANES> = hi.cast(); // wrapping addition let result = self.low + hi; // `select` here compiles to a blend operation // When `range.eq(0).none()` the compare and blend // operations are avoided. - let v: $ty = v.cast(); - return range.gt($unsigned::splat(0)).select(result, v); + let v: Simd<$ty, LANES> = v.cast(); + return range.lanes_gt(Simd::splat(0)).select(result, v); } // Replace only the failing lanes v = mask.select(v, rng.gen()); @@ -668,51 +679,16 @@ macro_rules! uniform_simd_int_impl { }; // bulk implementation - ($(($unsigned:ident, $signed:ident),)+ $u_scalar:ident) => { + ($(($unsigned:ident, $signed:ident)),+) => { $( - uniform_simd_int_impl!($unsigned, $unsigned, $u_scalar); - uniform_simd_int_impl!($signed, $unsigned, $u_scalar); + uniform_simd_int_impl!($unsigned, $unsigned); + uniform_simd_int_impl!($signed, $unsigned); )+ }; } #[cfg(feature = "simd_support")] -uniform_simd_int_impl! { - (u64x2, i64x2), - (u64x4, i64x4), - (u64x8, i64x8), - u64 -} - -#[cfg(feature = "simd_support")] -uniform_simd_int_impl! { - (u32x2, i32x2), - (u32x4, i32x4), - (u32x8, i32x8), - (u32x16, i32x16), - u32 -} - -#[cfg(feature = "simd_support")] -uniform_simd_int_impl! { - (u16x2, i16x2), - (u16x4, i16x4), - (u16x8, i16x8), - (u16x16, i16x16), - (u16x32, i16x32), - u16 -} - -#[cfg(feature = "simd_support")] -uniform_simd_int_impl! { - (u8x2, i8x2), - (u8x4, i8x4), - (u8x8, i8x8), - (u8x16, i8x16), - (u8x32, i8x32), - (u8x64, i8x64), - u8 -} +uniform_simd_int_impl! { (u8, i8), (u16, i16), (u32, i32), (u64, i64) } impl SampleUniform for char { type Sampler = UniformChar; @@ -847,7 +823,7 @@ macro_rules! uniform_float_impl { loop { let mask = (scale * max_rand + low).ge_mask(high); - if mask.none() { + if !mask.any() { break; } scale = scale.decrease_masked(mask); @@ -886,7 +862,7 @@ macro_rules! uniform_float_impl { loop { let mask = (scale * max_rand + low).gt_mask(high); - if mask.none() { + if !mask.any() { break; } scale = scale.decrease_masked(mask); @@ -899,11 +875,11 @@ macro_rules! uniform_float_impl { fn sample(&self, rng: &mut R) -> Self::X { // Generate a value in the range [1, 2) - let value1_2 = (rng.gen::<$uty>() >> $bits_to_discard).into_float_with_exponent(0); + let value1_2 = (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); // Get a value in the range [0, 1) in order to avoid // overflowing into infinity when multiplying with scale - let value0_1 = value1_2 - 1.0; + let value0_1 = value1_2 - <$ty>::splat(1.0); // We don't use `f64::mul_add`, because it is not available with // `no_std`. Furthermore, it is slower for some targets (but @@ -939,11 +915,11 @@ macro_rules! uniform_float_impl { loop { // Generate a value in the range [1, 2) let value1_2 = - (rng.gen::<$uty>() >> $bits_to_discard).into_float_with_exponent(0); + (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); // Get a value in the range [0, 1) in order to avoid // overflowing into infinity when multiplying with scale - let value0_1 = value1_2 - 1.0; + let value0_1 = value1_2 - <$ty>::splat(1.0); // Doing multiply before addition allows some architectures // to use a single instruction. @@ -1184,7 +1160,7 @@ mod tests { _ => panic!("`UniformDurationMode` was not serialized/deserialized correctly") } } - + #[test] #[cfg(feature = "serde1")] fn test_uniform_serialization() { @@ -1289,8 +1265,8 @@ mod tests { ($ty::splat(10), $ty::splat(127)), ($ty::splat($scalar::MIN), $ty::splat($scalar::MAX)), ], - |x: $ty, y| x.le(y).all(), - |x: $ty, y| x.lt(y).all() + |x: $ty, y| x.lanes_le(y).all(), + |x: $ty, y| x.lanes_lt(y).all() );)* }}; } @@ -1298,8 +1274,8 @@ mod tests { #[cfg(feature = "simd_support")] { - t!(u8x2, u8x4, u8x8, u8x16, u8x32, u8x64 => u8); - t!(i8x2, i8x4, i8x8, i8x16, i8x32, i8x64 => i8); + t!(u8x4, u8x8, u8x16, u8x32, u8x64 => u8); + t!(i8x4, i8x8, i8x16, i8x32, i8x64 => i8); t!(u16x2, u16x4, u16x8, u16x16, u16x32 => u16); t!(i16x2, i16x4, i16x8, i16x16, i16x32 => i16); t!(u32x2, u32x4, u32x8, u32x16 => u32); @@ -1351,7 +1327,7 @@ mod tests { (-::core::$f_scalar::MAX * 0.2, ::core::$f_scalar::MAX * 0.7), ]; for &(low_scalar, high_scalar) in v.iter() { - for lane in 0..<$ty>::lanes() { + for lane in 0..<$ty>::LANES { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); let my_uniform = Uniform::new(low, high); @@ -1474,7 +1450,7 @@ mod tests { (::std::$f_scalar::NEG_INFINITY, ::std::$f_scalar::INFINITY), ]; for &(low_scalar, high_scalar) in v.iter() { - for lane in 0..<$ty>::lanes() { + for lane in 0..<$ty>::LANES { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); assert!(catch_unwind(|| range(low, high)).is_err()); diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index 89da5fd7aad..689a4a1d8ea 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -8,7 +8,7 @@ //! Math helper functions -#[cfg(feature = "simd_support")] use packed_simd::*; +#[cfg(feature = "simd_support")] use core::simd::*; pub(crate) trait WideningMultiply { @@ -45,7 +45,7 @@ macro_rules! wmul_impl { let y: $wide = self.cast(); let x: $wide = x.cast(); let tmp = y * x; - let hi: $ty = (tmp >> $shift).cast(); + let hi: $ty = (tmp >> Simd::splat($shift)).cast(); let lo: $ty = tmp.cast(); (hi, lo) } @@ -99,19 +99,20 @@ macro_rules! wmul_impl_large { #[inline(always)] fn wmul(self, b: $ty) -> Self::Output { // needs wrapping multiplication - const LOWER_MASK: $scalar = !0 >> $half; + const LOWER_MASK: $ty = <$ty>::splat(!0 >> $half); + const HALF: $ty = <$ty>::splat($half); let mut low = (self & LOWER_MASK) * (b & LOWER_MASK); - let mut t = low >> $half; + let mut t = low >> HALF; low &= LOWER_MASK; - t += (self >> $half) * (b & LOWER_MASK); - low += (t & LOWER_MASK) << $half; - let mut high = t >> $half; - t = low >> $half; + t += (self >> HALF) * (b & LOWER_MASK); + low += (t & LOWER_MASK) << HALF; + let mut high = t >> HALF; + t = low >> HALF; low &= LOWER_MASK; - t += (b >> $half) * (self & LOWER_MASK); - low += (t & LOWER_MASK) << $half; - high += t >> $half; - high += (self >> $half) * (b >> $half); + t += (b >> HALF) * (self & LOWER_MASK); + low += (t & LOWER_MASK) << HALF; + high += t >> HALF; + high += (self >> HALF) * (b >> HALF); (high, low) } @@ -148,7 +149,6 @@ mod simd_wmul { #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; wmul_impl! { - (u8x2, u16x2), (u8x4, u16x4), (u8x8, u16x8), (u8x16, u16x16), @@ -167,16 +167,14 @@ mod simd_wmul { // means `wmul` can be implemented with only two instructions. #[allow(unused_macros)] macro_rules! wmul_impl_16 { - ($ty:ident, $intrinsic:ident, $mulhi:ident, $mullo:ident) => { + ($ty:ident, $mulhi:ident, $mullo:ident) => { impl WideningMultiply for $ty { type Output = ($ty, $ty); #[inline(always)] fn wmul(self, x: $ty) -> Self::Output { - let b = $intrinsic::from_bits(x); - let a = $intrinsic::from_bits(self); - let hi = $ty::from_bits(unsafe { $mulhi(a, b) }); - let lo = $ty::from_bits(unsafe { $mullo(a, b) }); + let hi = unsafe { $mulhi(self.into(), x.into()) }.into(); + let lo = unsafe { $mullo(self.into(), x.into()) }.into(); (hi, lo) } } @@ -184,11 +182,11 @@ mod simd_wmul { } #[cfg(target_feature = "sse2")] - wmul_impl_16! { u16x8, __m128i, _mm_mulhi_epu16, _mm_mullo_epi16 } + wmul_impl_16! { u16x8, _mm_mulhi_epu16, _mm_mullo_epi16 } #[cfg(target_feature = "avx2")] - wmul_impl_16! { u16x16, __m256i, _mm256_mulhi_epu16, _mm256_mullo_epi16 } - // FIXME: there are no `__m512i` types in stdsimd yet, so `wmul::` - // cannot use the same implementation. + wmul_impl_16! { u16x16, _mm256_mulhi_epu16, _mm256_mullo_epi16 } + #[cfg(target_feature = "avx512bw")] + wmul_impl_16! { u16x32, _mm512_mulhi_epu16, _mm512_mullo_epi16 } wmul_impl! { (u32x2, u64x2), @@ -199,6 +197,7 @@ mod simd_wmul { // TODO: optimize, this seems to seriously slow things down wmul_impl_large! { (u8x64,) u8, 4 } + #[cfg(not(target_feature = "avx512bw"))] wmul_impl_large! { (u16x32,) u16, 8 } wmul_impl_large! { (u32x16,) u32, 16 } wmul_impl_large! { (u64x2, u64x4, u64x8,) u64, 32 } @@ -229,6 +228,10 @@ pub(crate) trait FloatSIMDUtils { // value, not by retaining the binary representation. type UInt; fn cast_from_int(i: Self::UInt) -> Self; + + type Scalar; + fn replace(self, index: usize, new_value: Self::Scalar) -> Self; + fn extract(self, index: usize) -> Self::Scalar; } /// Implement functions available in std builds but missing from core primitives @@ -243,26 +246,23 @@ pub(crate) trait Float: Sized { /// Implement functions on f32/f64 to give them APIs similar to SIMD types pub(crate) trait FloatAsSIMD: Sized { - #[inline(always)] - fn lanes() -> usize { - 1 - } + const LANES: usize = 1; #[inline(always)] fn splat(scalar: Self) -> Self { scalar } +} + +pub(crate) trait IntAsSIMD: Sized { #[inline(always)] - fn extract(self, index: usize) -> Self { - debug_assert_eq!(index, 0); - self - } - #[inline(always)] - fn replace(self, index: usize, new_value: Self) -> Self { - debug_assert_eq!(index, 0); - new_value + fn splat(scalar: Self) -> Self { + scalar } } +impl IntAsSIMD for u32 {} +impl IntAsSIMD for u64 {} + pub(crate) trait BoolAsSIMD: Sized { fn any(self) -> bool; fn all(self) -> bool; @@ -308,6 +308,7 @@ macro_rules! scalar_float_impl { impl FloatSIMDUtils for $ty { type Mask = bool; + type Scalar = $ty; type UInt = $uty; #[inline(always)] @@ -350,6 +351,18 @@ macro_rules! scalar_float_impl { fn cast_from_int(i: Self::UInt) -> Self { i as $ty } + + #[inline] + fn replace(self, index: usize, new_value: Self::Scalar) -> Self { + debug_assert_eq!(index, 0); + new_value + } + + #[inline] + fn extract(self, index: usize) -> Self::Scalar { + debug_assert_eq!(index, 0); + self + } } impl FloatAsSIMD for $ty {} @@ -362,42 +375,42 @@ scalar_float_impl!(f64, u64); #[cfg(feature = "simd_support")] macro_rules! simd_impl { - ($ty:ident, $f_scalar:ident, $mty:ident, $uty:ident) => { - impl FloatSIMDUtils for $ty { - type Mask = $mty; - type UInt = $uty; + ($fty:ident, $uty:ident) => { + impl FloatSIMDUtils for Simd<$fty, LANES> + where LaneCount: SupportedLaneCount + { + type Mask = Mask<<$fty as SimdElement>::Mask, LANES>; + type Scalar = $fty; + type UInt = Simd<$uty, LANES>; #[inline(always)] fn all_lt(self, other: Self) -> bool { - self.lt(other).all() + self.lanes_lt(other).all() } #[inline(always)] fn all_le(self, other: Self) -> bool { - self.le(other).all() + self.lanes_le(other).all() } #[inline(always)] fn all_finite(self) -> bool { - self.finite_mask().all() + self.is_finite().all() } #[inline(always)] fn finite_mask(self) -> Self::Mask { - // This can possibly be done faster by checking bit patterns - let neg_inf = $ty::splat(::core::$f_scalar::NEG_INFINITY); - let pos_inf = $ty::splat(::core::$f_scalar::INFINITY); - self.gt(neg_inf) & self.lt(pos_inf) + self.is_finite() } #[inline(always)] fn gt_mask(self, other: Self) -> Self::Mask { - self.gt(other) + self.lanes_gt(other) } #[inline(always)] fn ge_mask(self, other: Self) -> Self::Mask { - self.ge(other) + self.lanes_ge(other) } #[inline(always)] @@ -406,24 +419,32 @@ macro_rules! simd_impl { // true, and 0 for false. Adding that to the binary // representation of a float means subtracting one from // the binary representation, resulting in the next lower - // value representable by $ty. This works even when the + // value representable by $fty. This works even when the // current value is infinity. debug_assert!(mask.any(), "At least one lane must be set"); - <$ty>::from_bits(<$uty>::from_bits(self) + <$uty>::from_bits(mask)) + Self::from_bits(self.to_bits() + mask.to_int().cast()) } #[inline] fn cast_from_int(i: Self::UInt) -> Self { i.cast() } + + #[inline] + fn replace(mut self, index: usize, new_value: Self::Scalar) -> Self { + self.as_mut_array()[index] = new_value; + self + } + + #[inline] + fn extract(self, index: usize) -> Self::Scalar { + self.as_array()[index] + } } }; } -#[cfg(feature="simd_support")] simd_impl! { f32x2, f32, m32x2, u32x2 } -#[cfg(feature="simd_support")] simd_impl! { f32x4, f32, m32x4, u32x4 } -#[cfg(feature="simd_support")] simd_impl! { f32x8, f32, m32x8, u32x8 } -#[cfg(feature="simd_support")] simd_impl! { f32x16, f32, m32x16, u32x16 } -#[cfg(feature="simd_support")] simd_impl! { f64x2, f64, m64x2, u64x2 } -#[cfg(feature="simd_support")] simd_impl! { f64x4, f64, m64x4, u64x4 } -#[cfg(feature="simd_support")] simd_impl! { f64x8, f64, m64x8, u64x8 } +#[cfg(feature = "simd_support")] +simd_impl!(f32, u32); +#[cfg(feature = "simd_support")] +simd_impl!(f64, u64); diff --git a/src/lib.rs b/src/lib.rs index 6d847180111..ef5c8a5a56c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] -#![cfg_attr(feature = "simd_support", feature(stdsimd))] +#![cfg_attr(feature = "simd_support", feature(stdsimd, portable_simd))] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![allow( clippy::float_cmp, From d4b8748004ee5c0fa41aa6b430ca701dd982605b Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Mon, 11 Jul 2022 11:29:13 -0700 Subject: [PATCH 251/443] fix simd ints, clarify mask behavior --- src/distributions/integer.rs | 27 +++++++++------------------ src/distributions/other.rs | 2 ++ 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index d905044ed1e..c660095bdf7 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -138,31 +138,22 @@ macro_rules! simd_impl { /// Requires nightly Rust and the [`simd_support`] feature /// /// [`simd_support`]: https://github.com/rust-random/rand#crate-features - impl Distribution<$ty> for Standard { + impl Distribution> for Standard + where + LaneCount: SupportedLaneCount, + { #[inline] - fn sample(&self, rng: &mut R) -> $ty { - // TODO: impl this generically once const generics are robust enough - let mut vec: Simd() }> = Default::default(); - rng.fill_bytes(vec.as_mut_array()); - // NOTE: replace with `to_le` if added to core::simd - #[cfg(not(target_endian = "little"))] - { - vec = vec.reverse(); - } - // SAFETY: we know u8xN and $ty have the same size - unsafe { mem::transmute_copy(&vec) } + fn sample(&self, rng: &mut R) -> Simd<$ty, LANES> { + let mut vec = Simd::default(); + rng.fill(vec.as_mut_array().as_mut_slice()); + vec } } )+}; } #[cfg(feature = "simd_support")] -simd_impl!( - i8x4, i8x8, i8x16, i8x32, i8x64, i16x2, i16x4, i16x8, i16x16, i16x32, i32x2, i32x4, i32x8, - i32x16, i64x2, i64x4, i64x8, isizex2, isizex4, isizex8, u8x4, u8x8, u8x16, u8x32, u8x64, u16x2, - u16x4, u16x8, u16x16, u16x32, u32x2, u32x4, u32x8, u32x16, u64x2, u64x4, u64x8, usizex2, - usizex4, usizex8 -); +simd_impl!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] intrinsic_impl!(__m128i, __m256i); diff --git a/src/distributions/other.rs b/src/distributions/other.rs index b3b5e4e0201..b53977e1052 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -174,6 +174,8 @@ where { #[inline] fn sample(&self, rng: &mut R) -> Mask { + // `MaskElement` must be a signed integer, so this is equivalent + // to the scalar `i32 < 0` method rng.gen().lanes_lt(Simd::default()) } } From f89f15fc1f846c53de4cd830c997314112dab0e4 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 4 Aug 2022 20:42:35 +0000 Subject: [PATCH 252/443] fix doc link --- src/distributions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 9b0a8867000..716536d4ee8 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -221,7 +221,7 @@ use crate::Rng; /// [`Uniform`]: uniform::Uniform /// [`Wrapping`]: std::num::Wrapping /// [`NonZeroU8`]: std::num::NonZeroU8 -/// [`__m128i`]: https://doc.rust-lang.org/nightly/core/arch/x86/struct.__m128i.html +/// [`__m128i`]: https://doc.rust-lang.org/core/arch/x86/struct.__m128i.html /// [`u32x4`]: std::simd::u32x4 /// [`f32x4`]: std::simd::f32x4 /// [`mask32x4`]: std::simd::mask32x4 From 2fab15dcd716fc568c9ca26e75465eef19a1f07e Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Sun, 7 Aug 2022 17:22:19 -0700 Subject: [PATCH 253/443] fix stdsimd, add mask opt notes --- src/distributions/integer.rs | 6 +++--- src/distributions/other.rs | 15 +++++++++++---- src/distributions/uniform.rs | 14 +++++++------- src/distributions/utils.rs | 36 ++++++++++++++++++------------------ 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index c660095bdf7..418eea9ff13 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -114,7 +114,7 @@ impl_nzint!(NonZeroU64, NonZeroU64::new); impl_nzint!(NonZeroU128, NonZeroU128::new); impl_nzint!(NonZeroUsize, NonZeroUsize::new); -macro_rules! intrinsic_impl { +macro_rules! x86_intrinsic_impl { ($($intrinsic:ident),+) => {$( /// Available only on x86/64 platforms impl Distribution<$intrinsic> for Standard { @@ -156,12 +156,12 @@ macro_rules! simd_impl { simd_impl!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -intrinsic_impl!(__m128i, __m256i); +x86_intrinsic_impl!(__m128i, __m256i); #[cfg(all( any(target_arch = "x86", target_arch = "x86_64"), feature = "simd_support" ))] -intrinsic_impl!(__m512i); +x86_intrinsic_impl!(__m512i); #[cfg(test)] mod tests { diff --git a/src/distributions/other.rs b/src/distributions/other.rs index b53977e1052..cb1b801f5b5 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -23,7 +23,7 @@ use serde::{Serialize, Deserialize}; #[cfg(feature = "min_const_gen")] use core::mem::{self, MaybeUninit}; #[cfg(feature = "simd_support")] -use core::simd::{Mask, Simd, LaneCount, SupportedLaneCount, MaskElement, SimdElement}; +use core::simd::*; // ----- Sampling distributions ----- @@ -161,22 +161,29 @@ impl Distribution for Standard { /// let x = rng.gen::().select(b, a); /// ``` /// -/// Since most bits are unused you could also generate only as many bits as you need. +/// Since most bits are unused you could also generate only as many bits as you need, i.e.: +/// ``` +/// let x = u16x8::splat(rng.gen:: as u16); +/// let mask = u16x8::splat(1) << u16x8::from([0, 1, 2, 3, 4, 5, 6, 7]); +/// let rand_mask = (x & mask).simd_eq(mask); +/// ``` /// /// [`_mm_blendv_epi8`]: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8&ig_expand=514/ /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[cfg(feature = "simd_support")] impl Distribution> for Standard where - T: MaskElement + PartialOrd + SimdElement + Default, + T: MaskElement + Default, LaneCount: SupportedLaneCount, Standard: Distribution>, + Simd: SimdPartialOrd>, { #[inline] fn sample(&self, rng: &mut R) -> Mask { // `MaskElement` must be a signed integer, so this is equivalent // to the scalar `i32 < 0` method - rng.gen().lanes_lt(Simd::default()) + let var = rng.gen::>(); + var.simd_lt(Simd::default()) } } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index d9197c68045..a7b4cb1a777 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -606,7 +606,7 @@ macro_rules! uniform_simd_int_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.lanes_lt(high).all(), "Uniform::new called with `low >= high`"); + assert!(low.simd_lt(high).all(), "Uniform::new called with `low >= high`"); UniformSampler::new_inclusive(low, high - Simd::splat(1)) } @@ -618,7 +618,7 @@ macro_rules! uniform_simd_int_impl { { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.lanes_le(high).all(), + assert!(low.simd_le(high).all(), "Uniform::new_inclusive called with `low > high`"); let unsigned_max = Simd::splat(::core::$unsigned::MAX); @@ -626,7 +626,7 @@ macro_rules! uniform_simd_int_impl { // see https://doc.rust-lang.org/std/simd/struct.Simd.html let range: Simd<$unsigned, LANES> = ((high - low) + Simd::splat(1)).cast(); // `% 0` will panic at runtime. - let not_full_range = range.lanes_gt(Simd::splat(0)); + let not_full_range = range.simd_gt(Simd::splat(0)); // replacing 0 with `unsigned_max` allows a faster `select` // with bitwise OR let modulo = not_full_range.select(range, unsigned_max); @@ -660,7 +660,7 @@ macro_rules! uniform_simd_int_impl { let mut v: Simd<$unsigned, LANES> = rng.gen(); loop { let (hi, lo) = v.wmul(range); - let mask = lo.lanes_le(zone); + let mask = lo.simd_le(zone); if mask.all() { let hi: Simd<$ty, LANES> = hi.cast(); // wrapping addition @@ -669,7 +669,7 @@ macro_rules! uniform_simd_int_impl { // When `range.eq(0).none()` the compare and blend // operations are avoided. let v: Simd<$ty, LANES> = v.cast(); - return range.lanes_gt(Simd::splat(0)).select(result, v); + return range.simd_gt(Simd::splat(0)).select(result, v); } // Replace only the failing lanes v = mask.select(v, rng.gen()); @@ -1265,8 +1265,8 @@ mod tests { ($ty::splat(10), $ty::splat(127)), ($ty::splat($scalar::MIN), $ty::splat($scalar::MAX)), ], - |x: $ty, y| x.lanes_le(y).all(), - |x: $ty, y| x.lanes_lt(y).all() + |x: $ty, y| x.simd_le(y).all(), + |x: $ty, y| x.simd_lt(y).all() );)* }}; } diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index 689a4a1d8ea..895756462f9 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -99,20 +99,20 @@ macro_rules! wmul_impl_large { #[inline(always)] fn wmul(self, b: $ty) -> Self::Output { // needs wrapping multiplication - const LOWER_MASK: $ty = <$ty>::splat(!0 >> $half); - const HALF: $ty = <$ty>::splat($half); - let mut low = (self & LOWER_MASK) * (b & LOWER_MASK); - let mut t = low >> HALF; - low &= LOWER_MASK; - t += (self >> HALF) * (b & LOWER_MASK); - low += (t & LOWER_MASK) << HALF; - let mut high = t >> HALF; - t = low >> HALF; - low &= LOWER_MASK; - t += (b >> HALF) * (self & LOWER_MASK); - low += (t & LOWER_MASK) << HALF; - high += t >> HALF; - high += (self >> HALF) * (b >> HALF); + let lower_mask = <$ty>::splat(!0 >> $half); + let half = <$ty>::splat($half); + let mut low = (self & lower_mask) * (b & lower_mask); + let mut t = low >> half; + low &= lower_mask; + t += (self >> half) * (b & lower_mask); + low += (t & lower_mask) << half; + let mut high = t >> half; + t = low >> half; + low &= lower_mask; + t += (b >> half) * (self & lower_mask); + low += (t & lower_mask) << half; + high += t >> half; + high += (self >> half) * (b >> half); (high, low) } @@ -385,12 +385,12 @@ macro_rules! simd_impl { #[inline(always)] fn all_lt(self, other: Self) -> bool { - self.lanes_lt(other).all() + self.simd_lt(other).all() } #[inline(always)] fn all_le(self, other: Self) -> bool { - self.lanes_le(other).all() + self.simd_le(other).all() } #[inline(always)] @@ -405,12 +405,12 @@ macro_rules! simd_impl { #[inline(always)] fn gt_mask(self, other: Self) -> Self::Mask { - self.lanes_gt(other) + self.simd_gt(other) } #[inline(always)] fn ge_mask(self, other: Self) -> Self::Mask { - self.lanes_ge(other) + self.simd_ge(other) } #[inline(always)] From 949d70f6a5bcb74adb28c6d3183c92591122b988 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Sun, 7 Aug 2022 17:42:24 -0700 Subject: [PATCH 254/443] fix doc test --- src/distributions/other.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index cb1b801f5b5..184fce9d4e9 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -163,7 +163,12 @@ impl Distribution for Standard { /// /// Since most bits are unused you could also generate only as many bits as you need, i.e.: /// ``` -/// let x = u16x8::splat(rng.gen:: as u16); +/// #![feature(portable_simd)] +/// use std::simd::*; +/// use rand::prelude::*; +/// let mut rng = thread_rng(); +/// +/// let x = u16x8::splat(rng.gen::() as u16); /// let mask = u16x8::splat(1) << u16x8::from([0, 1, 2, 3, 4, 5, 6, 7]); /// let rand_mask = (x & mask).simd_eq(mask); /// ``` From d60ab387bf60eff36ec462a2519bee69109d6fec Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 11 Aug 2022 05:45:51 +0000 Subject: [PATCH 255/443] optimize simd wmul --- src/distributions/utils.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index 895756462f9..bddb0a4a599 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -31,7 +31,7 @@ macro_rules! wmul_impl { }; // simd bulk implementation - ($(($ty:ident, $wide:ident),)+, $shift:expr) => { + ($(($ty:ident, $wide:ty),)+, $shift:expr) => { $( impl WideningMultiply for $ty { type Output = ($ty, $ty); @@ -152,7 +152,8 @@ mod simd_wmul { (u8x4, u16x4), (u8x8, u16x8), (u8x16, u16x16), - (u8x32, u16x32),, + (u8x32, u16x32), + (u8x64, Simd),, 8 } @@ -162,6 +163,8 @@ mod simd_wmul { wmul_impl! { (u16x8, u32x8),, 16 } #[cfg(not(target_feature = "avx2"))] wmul_impl! { (u16x16, u32x16),, 16 } + #[cfg(not(target_feature = "avx512bw"))] + wmul_impl! { (u16x32, Simd),, 16 } // 16-bit lane widths allow use of the x86 `mulhi` instructions, which // means `wmul` can be implemented with only two instructions. @@ -191,15 +194,11 @@ mod simd_wmul { wmul_impl! { (u32x2, u64x2), (u32x4, u64x4), - (u32x8, u64x8),, + (u32x8, u64x8), + (u32x16, Simd),, 32 } - // TODO: optimize, this seems to seriously slow things down - wmul_impl_large! { (u8x64,) u8, 4 } - #[cfg(not(target_feature = "avx512bw"))] - wmul_impl_large! { (u16x32,) u16, 8 } - wmul_impl_large! { (u32x16,) u32, 16 } wmul_impl_large! { (u64x2, u64x4, u64x8,) u64, 32 } } From 2569e9d653e46c54c230366b058a8db08b6701ce Mon Sep 17 00:00:00 2001 From: Pyry Kontio Date: Sun, 14 Aug 2022 16:59:12 +0900 Subject: [PATCH 256/443] Mention disabling getrandom for wasm32-unknown-unknown in README (#1250) --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d276dfb2141..8c576b4ec7f 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,13 @@ unavailable. ### WASM support -The WASM target `wasm32-unknown-unknown` is not *automatically* supported by -`rand` or `getrandom`. To solve this, either use a different target such as -`wasm32-wasi` or add a direct dependency on `getrandom` with the `js` feature -(if the target supports JavaScript). See +Seeding entropy from OS on WASM target `wasm32-unknown-unknown` is not +*automatically* supported by `rand` or `getrandom`. If you are fine with +seeding the generator manually, you can disable the `getrandom` feature +and use the methods on the `SeedableRng` trait. To enable seeding from OS, +either use a different target such as `wasm32-wasi` or add a direct +dependency on `getrandom` with the `js` feature (if the target supports +JavaScript). See [getrandom#WebAssembly support](https://docs.rs/getrandom/latest/getrandom/#webassembly-support). # License From db80c40c4b0a8e06ef72e50386af72e9ecacc786 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 3 Aug 2022 15:14:37 +0100 Subject: [PATCH 257/443] Bump MSRV to 1.38.0: fixes crossbeam-utils dev-dependency --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa1ff740b2c..fc2b991cb3f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,7 +47,7 @@ jobs: # Test both windows-gnu and windows-msvc; use beta rust on one - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.36.0 # MSRV + toolchain: 1.38.0 # MSRV - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu @@ -85,13 +85,13 @@ jobs: cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --examples - name: Test rand (all stable features, non-MSRV) - if: ${{ matrix.toolchain != '1.36.0' }} + if: ${{ matrix.toolchain != '1.38.0' }} run: | cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng,min_const_gen - name: Test rand (all stable features, MSRV) - if: ${{ matrix.toolchain == '1.36.0' }} + if: ${{ matrix.toolchain == '1.38.0' }} run: | - # const generics are not stable on 1.36.0 + # const generics are not stable on 1.38.0 cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng - name: Test rand_core run: | From 6951164ec62a126b1b9d24727fa511026ee63383 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 3 Aug 2022 15:22:57 +0100 Subject: [PATCH 258/443] Bump MSRV to 1.51.0: support const generics by default --- .github/workflows/test.yml | 14 ++++---------- Cargo.toml | 4 ---- README.md | 2 -- src/distributions/mod.rs | 4 +--- src/distributions/other.rs | 27 --------------------------- src/rng.rs | 32 +------------------------------- 6 files changed, 6 insertions(+), 77 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc2b991cb3f..1086f094a72 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: env: RUSTDOCFLAGS: --cfg doc_cfg # --all builds all crates, but with default features for other crates (okay in this case) - run: cargo deadlinks --ignore-fragments -- --all --features nightly,serde1,getrandom,small_rng,min_const_gen + run: cargo deadlinks --ignore-fragments -- --all --features nightly,serde1,getrandom,small_rng test: runs-on: ${{ matrix.os }} @@ -47,7 +47,7 @@ jobs: # Test both windows-gnu and windows-msvc; use beta rust on one - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.38.0 # MSRV + toolchain: 1.51.0 # MSRV - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu @@ -77,21 +77,15 @@ jobs: cargo test --target ${{ matrix.target }} --all-features cargo test --target ${{ matrix.target }} --benches --features=nightly cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches - cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features min_const_gen + cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - name: Test rand run: | cargo test --target ${{ matrix.target }} --lib --tests --no-default-features cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --examples - - name: Test rand (all stable features, non-MSRV) - if: ${{ matrix.toolchain != '1.38.0' }} - run: | - cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng,min_const_gen - - name: Test rand (all stable features, MSRV) - if: ${{ matrix.toolchain == '1.38.0' }} + - name: Test rand (all stable features) run: | - # const generics are not stable on 1.38.0 cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng - name: Test rand_core run: | diff --git a/Cargo.toml b/Cargo.toml index d207177a720..91c731e3b02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,10 +50,6 @@ std_rng = ["rand_chacha"] # Option: enable SmallRng small_rng = [] -# Option: for rustc ≥ 1.51, enable generating random arrays of any size -# using min-const-generics -min_const_gen = [] - [workspace] members = [ "rand_core", diff --git a/README.md b/README.md index 8c576b4ec7f..4459d7555d3 100644 --- a/README.md +++ b/README.md @@ -128,8 +128,6 @@ Additionally, these features configure Rand: - `nightly` enables some optimizations requiring nightly Rust - `simd_support` (experimental) enables sampling of SIMD values (uniformly random SIMD integers and floats), requiring nightly Rust -- `min_const_gen` enables generating random arrays of - any size using min-const-generics, requiring Rust ≥ 1.51. Note that nightly features are not stable and therefore not all library and compiler versions will be compatible. This is especially true of Rand's diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 716536d4ee8..a923f879d22 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -162,11 +162,9 @@ use crate::Rng; /// compound types where all component types are supported: /// /// * Tuples (up to 12 elements): each element is generated sequentially. -/// * Arrays (up to 32 elements): each element is generated sequentially; +/// * Arrays: each element is generated sequentially; /// see also [`Rng::fill`] which supports arbitrary array length for integer /// and float types and tends to be faster for `u32` and smaller types. -/// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support -/// arrays larger than 32 elements. /// Note that [`Rng::fill`] and `Standard`'s array support are *not* equivalent: /// the former is optimised for integer types (using fewer RNG calls for /// element types smaller than the RNG word size), while the latter supports diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 184fce9d4e9..4cb31086734 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -20,7 +20,6 @@ use crate::Rng; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; -#[cfg(feature = "min_const_gen")] use core::mem::{self, MaybeUninit}; #[cfg(feature = "simd_support")] use core::simd::*; @@ -236,8 +235,6 @@ tuple_impl! {A, B, C, D, E, F, G, H, I, J} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K} tuple_impl! {A, B, C, D, E, F, G, H, I, J, K, L} -#[cfg(feature = "min_const_gen")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "min_const_gen")))] impl Distribution<[T; N]> for Standard where Standard: Distribution { @@ -253,30 +250,6 @@ where Standard: Distribution } } -#[cfg(not(feature = "min_const_gen"))] -macro_rules! array_impl { - // recursive, given at least one type parameter: - {$n:expr, $t:ident, $($ts:ident,)*} => { - array_impl!{($n - 1), $($ts,)*} - - impl Distribution<[T; $n]> for Standard where Standard: Distribution { - #[inline] - fn sample(&self, _rng: &mut R) -> [T; $n] { - [_rng.gen::<$t>(), $(_rng.gen::<$ts>()),*] - } - } - }; - // empty case: - {$n:expr,} => { - impl Distribution<[T; $n]> for Standard { - fn sample(&self, _rng: &mut R) -> [T; $n] { [] } - } - }; -} - -#[cfg(not(feature = "min_const_gen"))] -array_impl! {32, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,} - impl Distribution> for Standard where Standard: Distribution { diff --git a/src/rng.rs b/src/rng.rs index 79a9fbff46e..e82f18854e8 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -68,11 +68,9 @@ pub trait Rng: RngCore { /// /// # Arrays and tuples /// - /// The `rng.gen()` method is able to generate arrays (up to 32 elements) + /// The `rng.gen()` method is able to generate arrays /// and tuples (up to 12 elements), so long as all element types can be /// generated. - /// When using `rustc` ≥ 1.51, enable the `min_const_gen` feature to support - /// arrays larger than 32 elements. /// /// For arrays of integers, especially for those with small element types /// (< 64 bit), it will likely be faster to instead use [`Rng::fill`]. @@ -392,8 +390,6 @@ macro_rules! impl_fill { impl_fill!(u16, u32, u64, usize, u128,); impl_fill!(i8, i16, i32, i64, isize, i128,); -#[cfg_attr(doc_cfg, doc(cfg(feature = "min_const_gen")))] -#[cfg(feature = "min_const_gen")] impl Fill for [T; N] where [T]: Fill { @@ -402,32 +398,6 @@ where [T]: Fill } } -#[cfg(not(feature = "min_const_gen"))] -macro_rules! impl_fill_arrays { - ($n:expr,) => {}; - ($n:expr, $N:ident) => { - impl Fill for [T; $n] where [T]: Fill { - fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { - self[..].try_fill(rng) - } - } - }; - ($n:expr, $N:ident, $($NN:ident,)*) => { - impl_fill_arrays!($n, $N); - impl_fill_arrays!($n - 1, $($NN,)*); - }; - (!div $n:expr,) => {}; - (!div $n:expr, $N:ident, $($NN:ident,)*) => { - impl_fill_arrays!($n, $N); - impl_fill_arrays!(!div $n / 2, $($NN,)*); - }; -} -#[cfg(not(feature = "min_const_gen"))] -#[rustfmt::skip] -impl_fill_arrays!(32, N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,); -#[cfg(not(feature = "min_const_gen"))] -impl_fill_arrays!(!div 4096, N,N,N,N,N,N,N,); - #[cfg(test)] mod test { use super::*; From fbf06ff9198d5da9a5f2964c8947b889022673b5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 5 Aug 2022 09:30:05 +0100 Subject: [PATCH 259/443] Optimized path of sample_efraimidis_spirakis is stable This no longer requires nightly --- Cargo.toml | 2 +- README.md | 2 +- src/seq/index.rs | 80 ++++++++++++++---------------------------------- 3 files changed, 25 insertions(+), 59 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 91c731e3b02..bc39334db66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ features = ["small_rng", "serde1"] [features] # Meta-features: default = ["std", "std_rng"] -nightly = [] # enables performance optimizations requiring nightly rust +nightly = [] # some additions requiring nightly Rust serde1 = ["serde", "rand_core/serde1"] # Option (enabled by default): without "std" rand uses libcore; this option diff --git a/README.md b/README.md index 4459d7555d3..40d1353441e 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Optionally, the following dependencies can be enabled: Additionally, these features configure Rand: - `small_rng` enables inclusion of the `SmallRng` PRNG -- `nightly` enables some optimizations requiring nightly Rust +- `nightly` includes some additions requiring nightly Rust - `simd_support` (experimental) enables sampling of SIMD values (uniformly random SIMD integers and floats), requiring nightly Rust diff --git a/src/seq/index.rs b/src/seq/index.rs index b38e4649d1f..7682facd51e 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -267,9 +267,7 @@ where R: Rng + ?Sized { /// sometimes be useful to have the indices themselves so this is provided as /// an alternative. /// -/// This implementation uses `O(length + amount)` space and `O(length)` time -/// if the "nightly" feature is enabled, or `O(length)` space and -/// `O(length + amount * log length)` time otherwise. +/// This implementation uses `O(length + amount)` space and `O(length)` time. /// /// Panics if `amount > length`. #[cfg(feature = "std")] @@ -300,9 +298,7 @@ where /// /// This implementation uses the algorithm described by Efraimidis and Spirakis /// in this paper: https://doi.org/10.1016/j.ipl.2005.11.003 -/// It uses `O(length + amount)` space and `O(length)` time if the -/// "nightly" feature is enabled, or `O(length)` space and `O(length -/// + amount * log length)` time otherwise. +/// It uses `O(length + amount)` space and `O(length)` time. /// /// Panics if `amount > length`. #[cfg(feature = "std")] @@ -347,63 +343,33 @@ where } impl Eq for Element {} - #[cfg(feature = "nightly")] - { - let mut candidates = Vec::with_capacity(length.as_usize()); - let mut index = N::zero(); - while index < length { - let weight = weight(index.as_usize()).into(); - if !(weight >= 0.) { - return Err(WeightedError::InvalidWeight); - } - - let key = rng.gen::().powf(1.0 / weight); - candidates.push(Element { index, key }); - - index += N::one(); + let mut candidates = Vec::with_capacity(length.as_usize()); + let mut index = N::zero(); + while index < length { + let weight = weight(index.as_usize()).into(); + if !(weight >= 0.) { + return Err(WeightedError::InvalidWeight); } - // Partially sort the array to find the `amount` elements with the greatest - // keys. Do this by using `select_nth_unstable` to put the elements with - // the *smallest* keys at the beginning of the list in `O(n)` time, which - // provides equivalent information about the elements with the *greatest* keys. - let (_, mid, greater) - = candidates.select_nth_unstable(length.as_usize() - amount.as_usize()); - - let mut result: Vec = Vec::with_capacity(amount.as_usize()); - result.push(mid.index); - for element in greater { - result.push(element.index); - } - Ok(IndexVec::from(result)) - } - - #[cfg(not(feature = "nightly"))] - { - use alloc::collections::BinaryHeap; + let key = rng.gen::().powf(1.0 / weight); + candidates.push(Element { index, key }); - // Partially sort the array such that the `amount` elements with the largest - // keys are first using a binary max heap. - let mut candidates = BinaryHeap::with_capacity(length.as_usize()); - let mut index = N::zero(); - while index < length { - let weight = weight(index.as_usize()).into(); - if !(weight >= 0.) { - return Err(WeightedError::InvalidWeight); - } + index += N::one(); + } - let key = rng.gen::().powf(1.0 / weight); - candidates.push(Element { index, key }); + // Partially sort the array to find the `amount` elements with the greatest + // keys. Do this by using `select_nth_unstable` to put the elements with + // the *smallest* keys at the beginning of the list in `O(n)` time, which + // provides equivalent information about the elements with the *greatest* keys. + let (_, mid, greater) + = candidates.select_nth_unstable(length.as_usize() - amount.as_usize()); - index += N::one(); - } - - let mut result: Vec = Vec::with_capacity(amount.as_usize()); - while result.len() < amount.as_usize() { - result.push(candidates.pop().unwrap().index); - } - Ok(IndexVec::from(result)) + let mut result: Vec = Vec::with_capacity(amount.as_usize()); + result.push(mid.index); + for element in greater { + result.push(element.index); } + Ok(IndexVec::from(result)) } /// Randomly sample exactly `amount` indices from `0..length`, using Floyd's From 89a1336b934c68ddce548127c6f8afd910b35a18 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 15 Sep 2022 14:23:08 +0100 Subject: [PATCH 260/443] rand_core: update CHANGELOG for 0.6.4 (#1253) --- rand_core/CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index 17482d40887..75fcbc667c6 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,9 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.6.4] - 2021-08-20 -### Fixed +## [0.6.4] - 2022-09-15 - Fix unsoundness in `::next_u32` (#1160) +- Reduce use of `unsafe` and improve gen_bytes performance (#1180) +- Add `CryptoRngCore` trait (#1187, #1230) ## [0.6.3] - 2021-06-15 ### Changed From 2b4f00add718c844d7b25b784d055a8efa628314 Mon Sep 17 00:00:00 2001 From: Alex Touchet Date: Thu, 15 Sep 2022 23:41:28 -0700 Subject: [PATCH 261/443] Update listed rand_core version number (#1254) --- rand_core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_core/README.md b/rand_core/README.md index d32dd6853d0..14bd7d550bc 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -43,7 +43,7 @@ The traits and error types are also available via `rand`. The current version is: ``` -rand_core = "0.6.0" +rand_core = "0.6.4" ``` Rand libs have inter-dependencies and make use of the From 766c7eccd73a1f2768f7ce2a4469005a65f7f9a2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 30 Sep 2022 09:48:10 +0100 Subject: [PATCH 262/443] Doc: improve random, thread_rng, ThreadRng docs (#1257) * Use a custom Debug impl for ThreadRng * Adjust documentation of random, thread_rng, ThreadRng * Fix no-std build * Compatibility with older rustc --- rand_core/src/os.rs | 5 ++-- src/lib.rs | 33 +++-------------------- src/rng.rs | 2 +- src/rngs/thread.rs | 64 +++++++++++++++++++++++++++++++++------------ 4 files changed, 55 insertions(+), 49 deletions(-) diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index 6cd1b9cf5de..b43c9fdaf05 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -19,8 +19,9 @@ use getrandom::getrandom; /// The implementation is provided by the [getrandom] crate. Refer to /// [getrandom] documentation for details. /// -/// This struct is only available when specifying the crate feature `getrandom` -/// or `std`. When using the `rand` lib, it is also available as `rand::rngs::OsRng`. +/// This struct is available as `rand_core::OsRng` and as `rand::rngs::OsRng`. +/// In both cases, this requires the crate feature `getrandom` or `std` +/// (enabled by default in `rand` but not in `rand_core`). /// /// # Blocking and error handling /// diff --git a/src/lib.rs b/src/lib.rs index ef5c8a5a56c..755b5ba6e9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,36 +110,10 @@ use crate::distributions::{Distribution, Standard}; /// Generates a random value using the thread-local random number generator. /// -/// This is simply a shortcut for `thread_rng().gen()`. See [`thread_rng`] for -/// documentation of the entropy source and [`Standard`] for documentation of -/// distributions and type-specific generation. +/// This function is simply a shortcut for `thread_rng().gen()`: /// -/// # Provided implementations -/// -/// The following types have provided implementations that -/// generate values with the following ranges and distributions: -/// -/// * Integers (`i32`, `u32`, `isize`, `usize`, etc.): Uniformly distributed -/// over all values of the type. -/// * `char`: Uniformly distributed over all Unicode scalar values, i.e. all -/// code points in the range `0...0x10_FFFF`, except for the range -/// `0xD800...0xDFFF` (the surrogate code points). This includes -/// unassigned/reserved code points. -/// * `bool`: Generates `false` or `true`, each with probability 0.5. -/// * Floating point types (`f32` and `f64`): Uniformly distributed in the -/// half-open range `[0, 1)`. See notes below. -/// * Wrapping integers (`Wrapping`), besides the type identical to their -/// normal integer variants. -/// -/// Also supported is the generation of the following -/// compound types where all component types are supported: -/// -/// * Tuples (up to 12 elements): each element is generated sequentially. -/// * Arrays (up to 32 elements): each element is generated sequentially; -/// see also [`Rng::fill`] which supports arbitrary array length for integer -/// types and tends to be faster for `u32` and smaller types. -/// * `Option` first generates a `bool`, and if true generates and returns -/// `Some(value)` where `value: T`, otherwise returning `None`. +/// - See [`ThreadRng`] for documentation of the generator and security +/// - See [`Standard`] for documentation of supported types and distributions /// /// # Examples /// @@ -177,6 +151,7 @@ use crate::distributions::{Distribution, Standard}; /// ``` /// /// [`Standard`]: distributions::Standard +/// [`ThreadRng`]: rngs::ThreadRng #[cfg(all(feature = "std", feature = "std_rng"))] #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] #[inline] diff --git a/src/rng.rs b/src/rng.rs index e82f18854e8..c9f3a5f72e5 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -53,7 +53,7 @@ use core::{mem, slice}; /// # let v = foo(&mut thread_rng()); /// ``` pub trait Rng: RngCore { - /// Return a random value supporting the [`Standard`] distribution. + /// Return a random value via the [`Standard`] distribution. /// /// # Example /// diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index baebb1d99c7..673c9a879d5 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -11,6 +11,7 @@ use core::cell::UnsafeCell; use std::rc::Rc; use std::thread_local; +use std::fmt; use super::std::Core; use crate::rngs::adapter::ReseedingRng; @@ -39,31 +40,43 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// A reference to the thread-local generator /// +/// This type is a reference to a lazily-initialized thread-local generator. /// An instance can be obtained via [`thread_rng`] or via `ThreadRng::default()`. /// This handle is safe to use everywhere (including thread-local destructors), /// though it is recommended not to use inside a fork handler. /// The handle cannot be passed between threads (is not `Send` or `Sync`). /// -/// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance -/// and is automatically seeded from [`OsRng`]. +/// `ThreadRng` uses the same CSPRNG as [`StdRng`], ChaCha12. As with +/// [`StdRng`], the algorithm may be changed, subject to reasonable expectations +/// of security and performance. /// -/// Unlike `StdRng`, `ThreadRng` uses the [`ReseedingRng`] wrapper to reseed -/// the PRNG from fresh entropy every 64 kiB of random data as well as after a -/// fork on Unix (though not quite immediately; see documentation of -/// [`ReseedingRng`]). -/// Note that the reseeding is done as an extra precaution against side-channel -/// attacks and mis-use (e.g. if somehow weak entropy were supplied initially). -/// The PRNG algorithms used are assumed to be secure. +/// `ThreadRng` is automatically seeded from [`OsRng`] with periodic reseeding +/// (every 64 kiB, as well as "soon" after a fork on Unix — see [`ReseedingRng`] +/// documentation for details). +/// +/// Security must be considered relative to a thread model and validation +/// requirements. `ThreadRng` attempts to meet basic security considerations +/// for producing unpredictable random numbers: use a CSPRNG, use a +/// recommended platform-specific seed ([`OsRng`]), and avoid +/// leaking internal secrets e.g. via [`Debug`] implementation or serialization. +/// Memory is not zeroized on drop. /// /// [`ReseedingRng`]: crate::rngs::adapter::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync rng: Rc>>, } +/// Debug implementation does not leak internal state +impl fmt::Debug for ThreadRng { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "ThreadRng {{ .. }}") + } +} + thread_local!( // We require Rc<..> to avoid premature freeing when thread_rng is used // within thread-local destructors. See #968. @@ -77,13 +90,23 @@ thread_local!( } ); -/// Retrieve the lazily-initialized thread-local random number generator, -/// seeded by the system. Intended to be used in method chaining style, -/// e.g. `thread_rng().gen::()`, or cached locally, e.g. -/// `let mut rng = thread_rng();`. Invoked by the `Default` trait, making -/// `ThreadRng::default()` equivalent. +/// Access the thread-local generator +/// +/// Returns a reference to the local [`ThreadRng`], initializing the generator +/// on the first call on each thread. /// -/// For more information see [`ThreadRng`]. +/// Example usage: +/// ``` +/// use rand::Rng; +/// +/// # fn main() { +/// // rand::random() may be used instead of rand::thread_rng().gen(): +/// println!("A random boolean: {}", rand::random::()); +/// +/// let mut rng = rand::thread_rng(); +/// println!("A simulated die roll: {}", rng.gen_range(1..=6)); +/// # } +/// ``` #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] pub fn thread_rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); @@ -92,7 +115,7 @@ pub fn thread_rng() -> ThreadRng { impl Default for ThreadRng { fn default() -> ThreadRng { - crate::prelude::thread_rng() + thread_rng() } } @@ -140,4 +163,11 @@ mod test { r.gen::(); assert_eq!(r.gen_range(0..1), 0); } + + #[test] + fn test_debug_output() { + // We don't care about the exact output here, but it must not include + // private CSPRNG state or the cache stored by BlockRng! + assert_eq!(std::format!("{:?}", crate::thread_rng()), "ThreadRng { .. }"); + } } From 8d70f5017f870b2925309f91c8117106c2c1bdd2 Mon Sep 17 00:00:00 2001 From: ISibboI Date: Mon, 10 Oct 2022 15:40:37 +0200 Subject: [PATCH 263/443] Clarify documentation of `choose_weighted(_mut)` mentioning accurate behavior with floats (#1245) * Fix the documentation for `choose_weighted(_mut)` as discussed in #1243. * Mention that elements of zero weight are handled as expected by `WeightedIndex` as discussed in #1243. Additionally fix some minor issues. * Let the second example of `WeightedIndex` use floats to stress that they are handled correctly for the zero case. * Manually indent doc comments. --- src/distributions/weighted_index.rs | 12 +++++++----- src/seq/mod.rs | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 8252b172f7f..2af2446d7ba 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -25,8 +25,10 @@ use serde::{Serialize, Deserialize}; /// Sampling a `WeightedIndex` distribution returns the index of a randomly /// selected element from the iterator used when the `WeightedIndex` was /// created. The chance of a given element being picked is proportional to the -/// value of the element. The weights can use any type `X` for which an -/// implementation of [`Uniform`] exists. +/// weight of the element. The weights can use any type `X` for which an +/// implementation of [`Uniform`] exists. The implementation guarantees that +/// elements with zero weight are never picked, even when the weights are +/// floating point numbers. /// /// # Performance /// @@ -42,8 +44,8 @@ use serde::{Serialize, Deserialize}; /// weights of type `X`, where `N` is the number of weights. However, since /// `Vec` doesn't guarantee a particular growth strategy, additional memory /// might be allocated but not used. Since the `WeightedIndex` object also -/// contains, this might cause additional allocations, though for primitive -/// types, [`Uniform`] doesn't allocate any memory. +/// contains an instance of `X::Sampler`, this might cause additional allocations, +/// though for primitive types, [`Uniform`] doesn't allocate any memory. /// /// Sampling from `WeightedIndex` will result in a single call to /// `Uniform::sample` (method of the [`Distribution`] trait), which typically @@ -65,7 +67,7 @@ use serde::{Serialize, Deserialize}; /// println!("{}", choices[dist.sample(&mut rng)]); /// } /// -/// let items = [('a', 0), ('b', 3), ('c', 7)]; +/// let items = [('a', 0.0), ('b', 3.0), ('c', 7.0)]; /// let dist2 = WeightedIndex::new(items.iter().map(|item| item.1)).unwrap(); /// for _ in 0..100 { /// // 0% chance to print 'a', 30% chance to print 'b', 70% chance to print 'c' diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 069e9e6b19e..a03d8241728 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -123,21 +123,25 @@ pub trait SliceRandom { /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. /// /// For slices of length `n`, complexity is `O(n)`. - /// See also [`choose_weighted_mut`], [`distributions::weighted`]. + /// For more information about the underlying algorithm, + /// see [`distributions::WeightedIndex`]. + /// + /// See also [`choose_weighted_mut`]. /// /// # Example /// /// ``` /// use rand::prelude::*; /// - /// let choices = [('a', 2), ('b', 1), ('c', 1)]; + /// let choices = [('a', 2), ('b', 1), ('c', 1), ('d', 0)]; /// let mut rng = thread_rng(); - /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c' + /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c', + /// // and 'd' will never be printed /// println!("{:?}", choices.choose_weighted(&mut rng, |item| item.1).unwrap().0); /// ``` /// [`choose`]: SliceRandom::choose /// [`choose_weighted_mut`]: SliceRandom::choose_weighted_mut - /// [`distributions::weighted`]: crate::distributions::weighted + /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted( @@ -161,11 +165,14 @@ pub trait SliceRandom { /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. /// /// For slices of length `n`, complexity is `O(n)`. - /// See also [`choose_weighted`], [`distributions::weighted`]. + /// For more information about the underlying algorithm, + /// see [`distributions::WeightedIndex`]. + /// + /// See also [`choose_weighted`]. /// /// [`choose_mut`]: SliceRandom::choose_mut /// [`choose_weighted`]: SliceRandom::choose_weighted - /// [`distributions::weighted`]: crate::distributions::weighted + /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted_mut( From 23f8b2986e2e7a74cf07ff77122a051ad51bd722 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 11 Oct 2022 10:07:15 +0200 Subject: [PATCH 264/443] clarify shuffle docs (#1259) --- src/seq/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index a03d8241728..edff51fe615 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -235,6 +235,7 @@ pub trait SliceRandom { /// Shuffle a mutable slice in place. /// /// For slices of length `n`, complexity is `O(n)`. + /// The resulting permutation is picked uniformly from the set of all possible permutations. /// /// # Example /// From 387dd644a24dd7a675f0452f27aedddf9e65396a Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Wed, 9 Nov 2022 06:50:09 -0800 Subject: [PATCH 265/443] fix outdated choose_multiple_weighted docs (#1237) --- src/seq/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index edff51fe615..420ef253d5e 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -199,10 +199,9 @@ pub trait SliceRandom { /// If all of the weights are equal, even if they are all zero, each element has /// an equal likelihood of being selected. /// - /// The complexity of this method depends on the feature `partition_at_index`. - /// If the feature is enabled, then for slices of length `n`, the complexity - /// is `O(n)` space and `O(n)` time. Otherwise, the complexity is `O(n)` space and - /// `O(n * log amount)` time. + /// This implementation uses `O(length + amount)` space and `O(length)` time + /// if the "nightly" feature is enabled, or `O(length)` space and + /// `O(length + amount * log length)` time otherwise. /// /// # Example /// From 9720f110a69659f05a9f84a38346b570e63a9c56 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 10 Nov 2022 15:36:40 +0000 Subject: [PATCH 266/443] Update GitHub Actions (#1263) * examples/rayon-monte-carlo.rs: remove execute bit of file metadata * Document in README that MSRV is 1.51.0 This was changed in #1246 * Update GitHub Actions --- .github/workflows/gh-pages.yml | 20 ++++++++-------- .github/workflows/test.yml | 44 +++++++++++----------------------- README.md | 14 +++-------- examples/rayon-monte-carlo.rs | 0 rand_chacha/README.md | 2 +- rand_core/README.md | 2 +- rand_distr/README.md | 2 +- rand_pcg/README.md | 2 +- 8 files changed, 31 insertions(+), 55 deletions(-) mode change 100755 => 100644 examples/rayon-monte-carlo.rs diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 80c0ec3d965..a86271a2441 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -10,13 +10,9 @@ jobs: name: GH-pages documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true + uses: dtolnay/rust-toolchain@nightly - name: doc (rand) env: RUSTDOCFLAGS: --cfg doc_cfg @@ -24,8 +20,12 @@ jobs: run: | cargo doc --all --features nightly,serde1,getrandom,small_rng cp utils/redirect.html target/doc/index.html - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + - name: Setup Pages + uses: actions/configure-pages@v2 + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc + path: './target/doc' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1086f094a72..7d64a5352b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,13 +11,9 @@ jobs: name: Check doc runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true + uses: dtolnay/rust-toolchain@nightly - run: cargo install cargo-deadlinks - name: doc (rand) env: @@ -58,14 +54,12 @@ jobs: variant: minimal_versions steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal target: ${{ matrix.target }} toolchain: ${{ matrix.toolchain }} - override: true - run: ${{ matrix.deps }} - name: Maybe minimal versions if: ${{ matrix.variant == 'minimal_versions' }} @@ -113,16 +107,14 @@ jobs: toolchain: stable steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal target: ${{ matrix.target }} toolchain: ${{ matrix.toolchain }} - override: true - name: Cache cargo plugins - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ~/.cargo/bin/ key: ${{ runner.os }}-cargo-plugins @@ -141,7 +133,7 @@ jobs: test-miri: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain run: | rustup toolchain install nightly --component miri @@ -161,41 +153,33 @@ jobs: test-no-std: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - toolchain: nightly target: thumbv6m-none-eabi - override: true - name: Build top-level only run: cargo build --target=thumbv6m-none-eabi --no-default-features test-avr: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: nightly-2021-01-07 # Pinned compiler version due to https://github.com/rust-lang/compiler-builtins/issues/400 components: rust-src - override: true - name: Build top-level only run: cargo build -Z build-std=core --target=avr-unknown-gnu-atmega328 --no-default-features test-ios: runs-on: macos-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - toolchain: nightly target: aarch64-apple-ios - override: true - name: Build top-level only run: cargo build --target=aarch64-apple-ios diff --git a/README.md b/README.md index 40d1353441e..0952d8c6305 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A Rust library for random number generation, featuring: @@ -95,17 +95,9 @@ Some versions of Rand crates have been yanked ("unreleased"). Where this occurs, the crate's CHANGELOG *should* be updated with a rationale, and a search on the issue tracker with the keyword `yank` *should* uncover the motivation. -### Rust version requirements +### Rust version requirements (MSRV) -Since version 0.8, Rand requires **Rustc version 1.36 or greater**. -Rand 0.7 requires Rustc 1.32 or greater while versions 0.5 require Rustc 1.22 or -greater, and 0.4 and 0.3 (since approx. June 2017) require Rustc version 1.15 or -greater. Subsets of the Rand code may work with older Rust versions, but this is -not supported. - -Continuous Integration (CI) will always test the minimum supported Rustc version -(the MSRV). The current policy is that this can be updated in any -Rand release if required, but the change must be noted in the changelog. +This version of Rand requires Rustc >= 1.51.0. ## Crate Features diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs old mode 100755 new mode 100644 diff --git a/rand_chacha/README.md b/rand_chacha/README.md index 1a6920d94f8..df52ab44ba8 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) [![API](https://docs.rs/rand_chacha/badge.svg)](https://docs.rs/rand_chacha) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A cryptographically secure random number generator that uses the ChaCha algorithm. diff --git a/rand_core/README.md b/rand_core/README.md index 14bd7d550bc..a7362d197df 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_core) [![API](https://docs.rs/rand_core/badge.svg)](https://docs.rs/rand_core) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Core traits and error types of the [rand] library, plus tools for implementing RNGs. diff --git a/rand_distr/README.md b/rand_distr/README.md index 3fc2ea62ef9..9096e918fd9 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a full suite of random number distribution sampling routines. diff --git a/rand_pcg/README.md b/rand_pcg/README.md index 736a789035c..d2c9259f181 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_pcg) [![API](https://docs.rs/rand_pcg/badge.svg)](https://docs.rs/rand_pcg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a selection of PCG random number generators. From 8339afc7ee3fdd06adb92b6e77e4507111e814a8 Mon Sep 17 00:00:00 2001 From: Yaron Sheffer Date: Mon, 14 Nov 2022 11:05:54 +0000 Subject: [PATCH 267/443] Fix typo (#1264) --- src/rngs/thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 673c9a879d5..78cecde5755 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -54,7 +54,7 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// (every 64 kiB, as well as "soon" after a fork on Unix — see [`ReseedingRng`] /// documentation for details). /// -/// Security must be considered relative to a thread model and validation +/// Security must be considered relative to a threat model and validation /// requirements. `ThreadRng` attempts to meet basic security considerations /// for producing unpredictable random numbers: use a CSPRNG, use a /// recommended platform-specific seed ([`OsRng`]), and avoid From 21131af61d51cab98da583cf46903f38041670cc Mon Sep 17 00:00:00 2001 From: Frank Steffahn Date: Mon, 14 Nov 2022 20:37:49 +0900 Subject: [PATCH 268/443] Remove redundant AsRef/AsMut bounds (#1207) --- rand_core/src/block.rs | 10 ++-------- src/rngs/adapter/reseeding.rs | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index d311b68cfe6..a527dda2971 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -178,10 +178,7 @@ impl BlockRng { } } -impl> RngCore for BlockRng -where - ::Results: AsRef<[u32]> + AsMut<[u32]>, -{ +impl> RngCore for BlockRng { #[inline] fn next_u32(&mut self) -> u32 { if self.index >= self.results.as_ref().len() { @@ -346,10 +343,7 @@ impl BlockRng64 { } } -impl> RngCore for BlockRng64 -where - ::Results: AsRef<[u64]> + AsMut<[u64]>, -{ +impl> RngCore for BlockRng64 { #[inline] fn next_u32(&mut self) -> u32 { let mut index = self.index - self.half_used as usize; diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index ae3fcbb2fc2..5ab453c928e 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -113,7 +113,6 @@ where impl RngCore for ReseedingRng where R: BlockRngCore + SeedableRng, - ::Results: AsRef<[u32]> + AsMut<[u32]>, { #[inline(always)] fn next_u32(&mut self) -> u32 { From 0aca9028f20fbc7db8fe1ecd57ca472fd1a5dcb3 Mon Sep 17 00:00:00 2001 From: ironhaven Date: Mon, 14 Nov 2022 06:08:30 -0600 Subject: [PATCH 269/443] SmallRng uses wrong seed_from_u64 implementation (#1203) * Forward inner seed_from_u64 implmentation for SmallRng * increase tolerance of sparkline tests --- rand_distr/tests/pdf.rs | 8 ++++---- src/rngs/small.rs | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index eb766142752..14db18153a4 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -89,8 +89,8 @@ fn normal() { .fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) ); for (&d, &e) in diff.iter().zip(expected_error.iter()) { - // Difference larger than 3 standard deviations or cutoff - let tol = (3. * e).max(1e-4); + // Difference larger than 4 standard deviations or cutoff + let tol = (4. * e).max(1e-4); assert!(d <= tol, "Difference = {} * tol", d / tol); } } @@ -172,8 +172,8 @@ fn skew_normal() { .fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) ); for (&d, &e) in diff.iter().zip(expected_error.iter()) { - // Difference larger than 3 standard deviations or cutoff - let tol = (3. * e).max(1e-4); + // Difference larger than 4 standard deviations or cutoff + let tol = (4. * e).max(1e-4); assert!(d <= tol, "Difference = {} * tol", d / tol); } } diff --git a/src/rngs/small.rs b/src/rngs/small.rs index fb0e0d119b6..a3261757847 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -114,4 +114,9 @@ impl SeedableRng for SmallRng { fn from_rng(rng: R) -> Result { Rng::from_rng(rng).map(SmallRng) } + + #[inline(always)] + fn seed_from_u64(state: u64) -> Self { + SmallRng(Rng::seed_from_u64(state)) + } } From 7aa25d577e2df84a5156f824077bb7f6bdf28d97 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 14 Nov 2022 16:41:49 +0000 Subject: [PATCH 270/443] gh-pages action: add id-token write permission (#1265) * gh-pages action: add id-token write permission * gh-pages action: add environment --- .github/workflows/gh-pages.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index a86271a2441..f751f8c5d2d 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -1,5 +1,10 @@ name: gh-pages +permissions: + contents: read + pages: write + id-token: write + on: push: branches: @@ -9,23 +14,33 @@ jobs: deploy: name: GH-pages documentation runs-on: ubuntu-latest + environment: + name: github-pages + url: https://rust-random.github.io/rand/ + steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 + - name: Install toolchain uses: dtolnay/rust-toolchain@nightly - - name: doc (rand) + + - name: Build docs env: RUSTDOCFLAGS: --cfg doc_cfg # --all builds all crates, but with default features for other crates (okay in this case) run: | cargo doc --all --features nightly,serde1,getrandom,small_rng cp utils/redirect.html target/doc/index.html + - name: Setup Pages uses: actions/configure-pages@v2 + - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: './target/doc' + - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 From 19169cbce9931eea5ccb4f2cbf174fc9d3e8759d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 6 Dec 2022 19:01:10 +0000 Subject: [PATCH 271/443] Bump MSRV to 1.56 (Edition 2021) (#1269) * Bump MSRV to 1.56 (Edition 2021) * Apply Clippy suggestions * Bump edition and add rust-version field to Cargo.toml * CI AVR test: unpin nightly rust version * Disable AVR test * Bump crate version numbers for a breaking release --- .github/workflows/test.yml | 24 ++++++++++++------------ Cargo.toml | 11 ++++++----- README.md | 7 ++++--- rand_chacha/Cargo.toml | 7 ++++--- rand_chacha/README.md | 2 +- rand_core/Cargo.toml | 5 +++-- rand_core/README.md | 2 +- rand_core/src/block.rs | 4 ++-- rand_distr/Cargo.toml | 11 ++++++----- rand_distr/README.md | 2 +- rand_distr/benches/Cargo.toml | 5 +++-- rand_pcg/Cargo.toml | 7 ++++--- rand_pcg/README.md | 2 +- rustfmt.toml | 2 +- src/distributions/uniform.rs | 2 +- src/rngs/adapter/reseeding.rs | 4 ++-- src/seq/index.rs | 4 ++-- src/seq/mod.rs | 2 +- 18 files changed, 55 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d64a5352b7..50dd5e8672b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: # Test both windows-gnu and windows-msvc; use beta rust on one - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.51.0 # MSRV + toolchain: 1.56.0 # MSRV - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu @@ -161,17 +161,17 @@ jobs: - name: Build top-level only run: cargo build --target=thumbv6m-none-eabi --no-default-features - test-avr: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install toolchain - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2021-01-07 # Pinned compiler version due to https://github.com/rust-lang/compiler-builtins/issues/400 - components: rust-src - - name: Build top-level only - run: cargo build -Z build-std=core --target=avr-unknown-gnu-atmega328 --no-default-features + # Disabled due to lack of known working compiler versions (not older than our MSRV) + # test-avr: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - name: Install toolchain + # uses: dtolnay/rust-toolchain@nightly + # with: + # components: rust-src + # - name: Build top-level only + # run: cargo build -Z build-std=core --target=avr-unknown-gnu-atmega328 --no-default-features test-ios: runs-on: macos-latest diff --git a/Cargo.toml b/Cargo.toml index bc39334db66..71b430f5467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.8.5" +version = "0.9.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -13,7 +13,8 @@ Random number generators and other randomness functionality. keywords = ["random", "rng"] categories = ["algorithms", "no-std"] autobenches = true -edition = "2018" +edition = "2021" +rust-version = "1.56" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] @@ -59,17 +60,17 @@ members = [ ] [dependencies] -rand_core = { path = "rand_core", version = "0.6.0" } +rand_core = { path = "rand_core", version = "0.7.0" } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } -rand_chacha = { path = "rand_chacha", version = "0.3.0", default-features = false, optional = true } +rand_chacha = { path = "rand_chacha", version = "0.4.0", default-features = false, optional = true } [target.'cfg(unix)'.dependencies] # Used for fork protection (reseeding.rs) libc = { version = "0.2.22", optional = true, default-features = false } [dev-dependencies] -rand_pcg = { path = "rand_pcg", version = "0.3.0" } +rand_pcg = { path = "rand_pcg", version = "0.4.0" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" diff --git a/README.md b/README.md index 0952d8c6305..c4704f3dc44 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A Rust library for random number generation, featuring: @@ -95,9 +95,10 @@ Some versions of Rand crates have been yanked ("unreleased"). Where this occurs, the crate's CHANGELOG *should* be updated with a rationale, and a search on the issue tracker with the keyword `yank` *should* uncover the motivation. -### Rust version requirements (MSRV) +### Rust version requirements -This version of Rand requires Rustc >= 1.51.0. +The Minimum Supported Rust Version (MSRV) is `rustc >= 1.56.0`. +Older releases may work (depending on feature configuration) but are untested. ## Crate Features diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index c4f5c113142..7584b788265 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_chacha" -version = "0.3.1" +version = "0.4.0" authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,10 +12,11 @@ ChaCha random number generator """ keywords = ["random", "rng", "chacha"] categories = ["algorithms", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56" [dependencies] -rand_core = { path = "../rand_core", version = "0.6.0" } +rand_core = { path = "../rand_core", version = "0.7.0" } ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/rand_chacha/README.md b/rand_chacha/README.md index df52ab44ba8..851490e22aa 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) [![API](https://docs.rs/rand_chacha/badge.svg)](https://docs.rs/rand_chacha) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A cryptographically secure random number generator that uses the ChaCha algorithm. diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index bfaa029bada..a3640068e00 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.6.4" +version = "0.7.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,7 +12,8 @@ Core random number generator traits and tools for implementation. """ keywords = ["random", "rng"] categories = ["algorithms", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56" [package.metadata.docs.rs] # To build locally: diff --git a/rand_core/README.md b/rand_core/README.md index a7362d197df..4174ff19484 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_core) [![API](https://docs.rs/rand_core/badge.svg)](https://docs.rs/rand_core) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Core traits and error types of the [rand] library, plus tools for implementing RNGs. diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index a527dda2971..5a986895484 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -381,13 +381,13 @@ impl> RngCore for BlockRng64 { let mut read_len = 0; self.half_used = false; while read_len < dest.len() { - if self.index as usize >= self.results.as_ref().len() { + if self.index >= self.results.as_ref().len() { self.core.generate(&mut self.results); self.index = 0; } let (consumed_u64, filled_u8) = fill_via_u64_chunks( - &self.results.as_ref()[self.index as usize..], + &self.results.as_ref()[self.index..], &mut dest[read_len..], ); diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 32a5fcaf5ae..ff1400d6e6e 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.4.3" +version = "0.5.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,7 +12,8 @@ Sampling from random number distributions """ keywords = ["random", "rng", "distribution", "probability"] categories = ["algorithms", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [features] @@ -23,14 +24,14 @@ std_math = ["num-traits/std"] serde1 = ["serde", "rand/serde1"] [dependencies] -rand = { path = "..", version = "0.8.0", default-features = false } +rand = { path = "..", version = "0.9.0", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } [dev-dependencies] -rand_pcg = { version = "0.3.0", path = "../rand_pcg" } +rand_pcg = { version = "0.4.0", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "0.8.0", default-features = false, features = ["std_rng", "std", "small_rng"] } +rand = { path = "..", version = "0.9.0", default-features = false, features = ["std_rng", "std", "small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.13", features = [ "std" ] } # Special functions for testing distributions diff --git a/rand_distr/README.md b/rand_distr/README.md index 9096e918fd9..d11a3744c41 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a full suite of random number distribution sampling routines. diff --git a/rand_distr/benches/Cargo.toml b/rand_distr/benches/Cargo.toml index 093286d57df..aeba667b3b4 100644 --- a/rand_distr/benches/Cargo.toml +++ b/rand_distr/benches/Cargo.toml @@ -4,7 +4,8 @@ version = "0.0.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" description = "Criterion benchmarks of the rand_distr crate" -edition = "2018" +edition = "2021" +rust-version = "1.56" publish = false [workspace] @@ -19,4 +20,4 @@ rand_pcg = { path = "../../rand_pcg/" } [[bench]] name = "distributions" path = "src/distributions.rs" -harness = false \ No newline at end of file +harness = false diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 8ef7a3b5052..80099769275 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_pcg" -version = "0.3.1" +version = "0.4.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -12,13 +12,14 @@ Selected PCG random number generators """ keywords = ["random", "rng", "pcg"] categories = ["algorithms", "no-std"] -edition = "2018" +edition = "2021" +rust-version = "1.56" [features] serde1 = ["serde"] [dependencies] -rand_core = { path = "../rand_core", version = "0.6.0" } +rand_core = { path = "../rand_core", version = "0.7.0" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] diff --git a/rand_pcg/README.md b/rand_pcg/README.md index d2c9259f181..ce6d1f37c6c 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_pcg) [![API](https://docs.rs/rand_pcg/badge.svg)](https://docs.rs/rand_pcg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.51+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a selection of PCG random number generators. diff --git a/rustfmt.toml b/rustfmt.toml index 6a2d9d48215..ded1e7812fb 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -19,7 +19,7 @@ where_single_line = true # struct_field_align_threshold = 20 # Compatibility: -edition = "2018" # we require compatibility back to 1.32.0 +edition = "2021" # Misc: inline_attribute_width = 80 diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index a7b4cb1a777..49703b84e46 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -340,7 +340,7 @@ where Borrowed: SampleUniform { #[inline(always)] fn borrow(&self) -> &Borrowed { - *self + self } } diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index 5ab453c928e..b78b850a4a6 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -208,8 +208,8 @@ where ReseedingCore { inner: rng, reseeder, - threshold: threshold as i64, - bytes_until_reseed: threshold as i64, + threshold, + bytes_until_reseed: threshold, fork_counter: 0, } } diff --git a/src/seq/index.rs b/src/seq/index.rs index 7682facd51e..ecd7d9f7383 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -238,7 +238,7 @@ where R: Rng + ?Sized { if amount < 163 { const C: [[f32; 2]; 2] = [[1.6, 8.0 / 45.0], [10.0, 70.0 / 9.0]]; - let j = if length < 500_000 { 0 } else { 1 }; + let j = usize::from(length >= 500_000); let amount_fp = amount as f32; let m4 = C[0][j] * amount_fp; // Short-cut: when amount < 12, floyd's is always faster @@ -249,7 +249,7 @@ where R: Rng + ?Sized { } } else { const C: [f32; 2] = [270.0, 330.0 / 9.0]; - let j = if length < 500_000 { 0 } else { 1 }; + let j = usize::from(length >= 500_000); if (length as f32) < C[j] * (amount as f32) { sample_inplace(rng, length, amount) } else { diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 420ef253d5e..24c65bc9f08 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -641,7 +641,7 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> Iterator for SliceCho fn next(&mut self) -> Option { // TODO: investigate using SliceIndex::get_unchecked when stable - self.indices.next().map(|i| &self.slice[i as usize]) + self.indices.next().map(|i| &self.slice[i]) } fn size_hint(&self) -> (usize, Option) { From 50b9a447410860af8d6db9a208c3576886955874 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 7 Dec 2022 09:47:45 +0000 Subject: [PATCH 272/443] fill_via_chunks: mutate src on BE (small optimisation) (#1182) * fill_via_chunks: mutate src on BE (small optimisation) * Add doc to fill_via_chunks --- rand_core/src/block.rs | 4 +-- rand_core/src/impls.rs | 77 ++++++++++++++++++++++++------------------ 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 5a986895484..f813784ff2f 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -223,7 +223,7 @@ impl> RngCore for BlockRng { self.generate_and_set(0); } let (consumed_u32, filled_u8) = - fill_via_u32_chunks(&self.results.as_ref()[self.index..], &mut dest[read_len..]); + fill_via_u32_chunks(&mut self.results.as_mut()[self.index..], &mut dest[read_len..]); self.index += consumed_u32; read_len += filled_u8; @@ -387,7 +387,7 @@ impl> RngCore for BlockRng64 { } let (consumed_u64, filled_u8) = fill_via_u64_chunks( - &self.results.as_ref()[self.index..], + &mut self.results.as_mut()[self.index..], &mut dest[read_len..], ); diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 4b7688c5c80..8f99ef813a5 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -53,16 +53,14 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } trait Observable: Copy { - type Bytes: AsRef<[u8]>; - fn to_le_bytes(self) -> Self::Bytes; + fn to_le(self) -> Self; // Contract: observing self is memory-safe (implies no uninitialised padding) fn as_byte_slice(x: &[Self]) -> &[u8]; } impl Observable for u32 { - type Bytes = [u8; 4]; - fn to_le_bytes(self) -> Self::Bytes { - self.to_le_bytes() + fn to_le(self) -> Self { + self.to_le() } fn as_byte_slice(x: &[Self]) -> &[u8] { let ptr = x.as_ptr() as *const u8; @@ -71,9 +69,8 @@ impl Observable for u32 { } } impl Observable for u64 { - type Bytes = [u8; 8]; - fn to_le_bytes(self) -> Self::Bytes { - self.to_le_bytes() + fn to_le(self) -> Self { + self.to_le() } fn as_byte_slice(x: &[Self]) -> &[u8] { let ptr = x.as_ptr() as *const u8; @@ -82,28 +79,27 @@ impl Observable for u64 { } } -fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) { +/// Fill dest from src +/// +/// Returns `(n, byte_len)`. `src[..n]` is consumed (and possibly mutated), +/// `dest[..byte_len]` is filled. `src[n..]` and `dest[byte_len..]` are left +/// unaltered. +fn fill_via_chunks(src: &mut [T], dest: &mut [u8]) -> (usize, usize) { let size = core::mem::size_of::(); let byte_len = min(src.len() * size, dest.len()); let num_chunks = (byte_len + size - 1) / size; - if cfg!(target_endian = "little") { - // On LE we can do a simple copy, which is 25-50% faster: - dest[..byte_len].copy_from_slice(&T::as_byte_slice(&src[..num_chunks])[..byte_len]); - } else { - // This code is valid on all arches, but slower than the above: - let mut i = 0; - let mut iter = dest[..byte_len].chunks_exact_mut(size); - for chunk in &mut iter { - chunk.copy_from_slice(src[i].to_le_bytes().as_ref()); - i += 1; - } - let chunk = iter.into_remainder(); - if !chunk.is_empty() { - chunk.copy_from_slice(&src[i].to_le_bytes().as_ref()[..chunk.len()]); + // Byte-swap for portability of results. This must happen before copying + // since the size of dest is not guaranteed to be a multiple of T or to be + // sufficiently aligned. + if cfg!(target_endian = "big") { + for x in &mut src[..num_chunks] { + *x = x.to_le(); } } + dest[..byte_len].copy_from_slice(&T::as_byte_slice(&src[..num_chunks])[..byte_len]); + (num_chunks, byte_len) } @@ -112,6 +108,9 @@ fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) /// /// The return values are `(consumed_u32, filled_u8)`. /// +/// On big-endian systems, endianness of `src[..consumed_u32]` values is +/// swapped. No other adjustments to `src` are made. +/// /// `filled_u8` is the number of filled bytes in `dest`, which may be less than /// the length of `dest`. /// `consumed_u32` is the number of words consumed from `src`, which is the same @@ -137,7 +136,7 @@ fn fill_via_chunks(src: &[T], dest: &mut [u8]) -> (usize, usize) /// } /// } /// ``` -pub fn fill_via_u32_chunks(src: &[u32], dest: &mut [u8]) -> (usize, usize) { +pub fn fill_via_u32_chunks(src: &mut [u32], dest: &mut [u8]) -> (usize, usize) { fill_via_chunks(src, dest) } @@ -145,13 +144,17 @@ pub fn fill_via_u32_chunks(src: &[u32], dest: &mut [u8]) -> (usize, usize) { /// based RNG. /// /// The return values are `(consumed_u64, filled_u8)`. +/// +/// On big-endian systems, endianness of `src[..consumed_u64]` values is +/// swapped. No other adjustments to `src` are made. +/// /// `filled_u8` is the number of filled bytes in `dest`, which may be less than /// the length of `dest`. /// `consumed_u64` is the number of words consumed from `src`, which is the same /// as `filled_u8 / 8` rounded up. /// /// See `fill_via_u32_chunks` for an example. -pub fn fill_via_u64_chunks(src: &[u64], dest: &mut [u8]) -> (usize, usize) { +pub fn fill_via_u64_chunks(src: &mut [u64], dest: &mut [u8]) -> (usize, usize) { fill_via_chunks(src, dest) } @@ -175,33 +178,41 @@ mod test { #[test] fn test_fill_via_u32_chunks() { - let src = [1, 2, 3]; + let src_orig = [1, 2, 3]; + + let mut src = src_orig; let mut dst = [0u8; 11]; - assert_eq!(fill_via_u32_chunks(&src, &mut dst), (3, 11)); + assert_eq!(fill_via_u32_chunks(&mut src, &mut dst), (3, 11)); assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0]); + let mut src = src_orig; let mut dst = [0u8; 13]; - assert_eq!(fill_via_u32_chunks(&src, &mut dst), (3, 12)); + assert_eq!(fill_via_u32_chunks(&mut src, &mut dst), (3, 12)); assert_eq!(dst, [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0]); + let mut src = src_orig; let mut dst = [0u8; 5]; - assert_eq!(fill_via_u32_chunks(&src, &mut dst), (2, 5)); + assert_eq!(fill_via_u32_chunks(&mut src, &mut dst), (2, 5)); assert_eq!(dst, [1, 0, 0, 0, 2]); } #[test] fn test_fill_via_u64_chunks() { - let src = [1, 2]; + let src_orig = [1, 2]; + + let mut src = src_orig; let mut dst = [0u8; 11]; - assert_eq!(fill_via_u64_chunks(&src, &mut dst), (2, 11)); + assert_eq!(fill_via_u64_chunks(&mut src, &mut dst), (2, 11)); assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0]); + let mut src = src_orig; let mut dst = [0u8; 17]; - assert_eq!(fill_via_u64_chunks(&src, &mut dst), (2, 16)); + assert_eq!(fill_via_u64_chunks(&mut src, &mut dst), (2, 16)); assert_eq!(dst, [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]); + let mut src = src_orig; let mut dst = [0u8; 5]; - assert_eq!(fill_via_u64_chunks(&src, &mut dst), (1, 5)); + assert_eq!(fill_via_u64_chunks(&mut src, &mut dst), (1, 5)); assert_eq!(dst, [1, 0, 0, 0, 0]); } } From 0dddc2c5593fae7dc473b9f1d5774f1caa5e61c2 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Wed, 7 Dec 2022 02:18:04 -0800 Subject: [PATCH 273/443] Add read_adapter to avoid dynamic dispatch (#1267) * Add read_adapter to avoid dynamic dispatch * Get rid of the dyn Read impl since we can't deprecate it --- rand_core/src/lib.rs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 1234a566c05..70baf78dec4 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -41,8 +41,8 @@ use core::convert::AsMut; use core::default::Default; -#[cfg(feature = "std")] extern crate std; #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] extern crate std; #[cfg(feature = "alloc")] use alloc::boxed::Box; pub use error::Error; @@ -182,6 +182,13 @@ pub trait RngCore { /// `fill_bytes` may be implemented with /// `self.try_fill_bytes(dest).unwrap()` or more specific error handling. fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error>; + + /// Convert an [`RngCore`] to a [`RngReadAdapter`]. + #[cfg(feature = "std")] + fn read_adapter(&mut self) -> RngReadAdapter<'_, Self> + where Self: Sized { + RngReadAdapter { inner: self } + } } /// A marker trait used to indicate that an [`RngCore`] or [`BlockRngCore`] @@ -469,14 +476,37 @@ impl RngCore for Box { } } +/// Adapter that enables reading through a [`io::Read`](std::io::Read) from a [`RngCore`]. +/// +/// # Examples +/// +/// ```rust +/// # use std::{io, io::Read}; +/// # use std::fs::File; +/// # use rand_core::{OsRng, RngCore}; +/// +/// io::copy(&mut OsRng.read_adapter().take(100), &mut File::create("/tmp/random.bytes").unwrap()).unwrap(); +/// ``` #[cfg(feature = "std")] -impl std::io::Read for dyn RngCore { +pub struct RngReadAdapter<'a, R: RngCore + ?Sized> { + inner: &'a mut R, +} + +#[cfg(feature = "std")] +impl std::io::Read for RngReadAdapter<'_, R> { fn read(&mut self, buf: &mut [u8]) -> Result { - self.try_fill_bytes(buf)?; + self.inner.try_fill_bytes(buf)?; Ok(buf.len()) } } +#[cfg(feature = "std")] +impl std::fmt::Debug for RngReadAdapter<'_, R> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ReadAdapter").finish() + } +} + // Implement `CryptoRng` for references to a `CryptoRng`. impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {} From 81d7dc726443e9a057161356bcef0e6a770f9180 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 9 Dec 2022 11:50:09 +0200 Subject: [PATCH 274/443] build: harden test.yml permissions (#1274) Signed-off-by: Alex Signed-off-by: Alex --- .github/workflows/test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 50dd5e8672b..4039cb3a973 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ master, '0.[0-9]+' ] +permissions: + contents: read # to fetch code (actions/checkout) + jobs: check-doc: name: Check doc From fbd95860b461b04a2a0c02965b485192498189b5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 11 Dec 2022 17:47:51 +0000 Subject: [PATCH 275/443] Add Criterion as dev-dependency, fix CI for MSRV and minimal-versions (#1275) * Add criterion as dev-dependency * CI[minimal-versions]: require regex 1.5.1 --- .github/workflows/test.yml | 11 +- Cargo.lock.msrv | 707 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 3 files changed, 717 insertions(+), 2 deletions(-) create mode 100644 Cargo.lock.msrv diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4039cb3a973..a35e82db3df 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,8 @@ jobs: # Test both windows-gnu and windows-msvc; use beta rust on one - os: ubuntu-latest target: x86_64-unknown-linux-gnu - toolchain: 1.56.0 # MSRV + variant: MSRV + toolchain: 1.56.0 - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu @@ -58,6 +59,9 @@ jobs: steps: - uses: actions/checkout@v3 + - name: MSRV + if: ${{ matrix.variant == 'MSRV' }} + run: cp Cargo.lock.msrv Cargo.lock - name: Install toolchain uses: dtolnay/rust-toolchain@master with: @@ -66,7 +70,10 @@ jobs: - run: ${{ matrix.deps }} - name: Maybe minimal versions if: ${{ matrix.variant == 'minimal_versions' }} - run: cargo generate-lockfile -Z minimal-versions + run: | + cargo generate-lockfile -Z minimal-versions + # Overrides for dependencies with incorrect requirements (may need periodic updating) + cargo update -p regex --precise 1.5.1 - name: Maybe nightly if: ${{ matrix.toolchain == 'nightly' }} run: | diff --git a/Cargo.lock.msrv b/Cargo.lock.msrv new file mode 100644 index 00000000000..b173eb6d866 --- /dev/null +++ b/Cargo.lock.msrv @@ -0,0 +1,707 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "average" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843ec791d3f24503bbf72bbd5e49a3ab4dbb4bcd0a8ef6b0c908efa73caa27b1" +dependencies = [ + "easy-cast", + "float-ord", + "num-traits", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53da17d37dba964b9b3ecb5c5a1f193a2762c700e6829201e645b9381c99dc7" +dependencies = [ + "bitflags", + "clap_lex", + "indexmap", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "easy-cast" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd102ee8c418348759919b83b81cdbdc933ffe29740b903df448b4bafaa348e" +dependencies = [ + "libm", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + +[[package]] +name = "plotters" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0" +dependencies = [ + "bincode", + "criterion", + "libc", + "log", + "rand_chacha", + "rand_core", + "rand_pcg", + "rayon", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.4.0" +dependencies = [ + "ppv-lite86", + "rand_core", + "serde", + "serde_json", +] + +[[package]] +name = "rand_core" +version = "0.7.0" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "rand_distr" +version = "0.5.0" +dependencies = [ + "average", + "num-traits", + "rand", + "rand_pcg", + "serde", + "special", +] + +[[package]] +name = "rand_pcg" +version = "0.4.0" +dependencies = [ + "bincode", + "rand_core", + "serde", +] + +[[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[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 = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "special" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a65e074159b75dcf173a4733ab2188baac24967b5c8ec9ed87ae15fcbc7636" +dependencies = [ + "libc", +] + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "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.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 71b430f5467..da251903dd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,3 +74,4 @@ rand_pcg = { path = "rand_pcg", version = "0.4.0" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" +criterion = { version = "0.4" } From b9e7d84c3bf14705b4907c92d024e07972032e12 Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 22 Dec 2022 02:54:41 -0800 Subject: [PATCH 276/443] use partition_point in WeightedIndex (#1276) * use partition_point in WeightedIndex * fix partition_point * fix --- src/distributions/weighted_index.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 2af2446d7ba..b1b2071abc1 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -229,15 +229,7 @@ where X: SampleUniform + PartialOrd use ::core::cmp::Ordering; let chosen_weight = self.weight_distribution.sample(rng); // Find the first item which has a weight *higher* than the chosen weight. - self.cumulative_weights - .binary_search_by(|w| { - if *w <= chosen_weight { - Ordering::Less - } else { - Ordering::Greater - } - }) - .unwrap_err() + self.cumulative_weights.partition_point(|w| w <= &chosen_weight) } } From e97b5b68877adc204e7193db40257f6e346e528b Mon Sep 17 00:00:00 2001 From: Paul Crowley Date: Sun, 1 Jan 2023 01:49:32 -0800 Subject: [PATCH 277/443] Simpler and faster implementation of Floyd's F2 (#1277) The previous implementation used either `Vec::insert` or a second F-Y shuffling phase to achieve fair random order. Instead, use the random numbers already drawn to achieve a fair shuffle. --- CHANGELOG.md | 7 +++++++ src/seq/index.rs | 29 +++++++---------------------- src/seq/mod.rs | 2 +- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0872af6d39..b27a5edb493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [Unreleased API changing release] + +### Other +- Simpler and faster implementation of Floyd's F2 (#1277). This + changes some outputs from `rand::seq::index::sample` and + `rand::seq::SliceRandom::choose_multiple`. + ## [0.8.5] - 2021-08-20 ### Fixes - Fix build on non-32/64-bit architectures (#1144) diff --git a/src/seq/index.rs b/src/seq/index.rs index ecd7d9f7383..b7bc6a9b26a 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -380,33 +380,18 @@ where /// This implementation uses `O(amount)` memory and `O(amount^2)` time. fn sample_floyd(rng: &mut R, length: u32, amount: u32) -> IndexVec where R: Rng + ?Sized { - // For small amount we use Floyd's fully-shuffled variant. For larger - // amounts this is slow due to Vec::insert performance, so we shuffle - // afterwards. Benchmarks show little overhead from extra logic. - let floyd_shuffle = amount < 50; - + // Note that the values returned by `rng.gen_range()` can be + // inferred from the returned vector by working backwards from + // the last entry. This bijection proves the algorithm fair. debug_assert!(amount <= length); let mut indices = Vec::with_capacity(amount as usize); for j in length - amount..length { let t = rng.gen_range(0..=j); - if floyd_shuffle { - if let Some(pos) = indices.iter().position(|&x| x == t) { - indices.insert(pos, j); - continue; - } - } else if indices.contains(&t) { - indices.push(j); - continue; + if let Some(pos) = indices.iter().position(|&x| x == t) { + indices[pos] = j; } indices.push(t); } - if !floyd_shuffle { - // Reimplement SliceRandom::shuffle with smaller indices - for i in (1..amount).rev() { - // invariant: elements with index > i have been locked in place. - indices.swap(i as usize, rng.gen_range(0..=i) as usize); - } - } IndexVec::from(indices) } @@ -628,8 +613,8 @@ mod test { ); }; - do_test(10, 6, &[8, 0, 3, 5, 9, 6]); // floyd - do_test(25, 10, &[18, 15, 14, 9, 0, 13, 5, 24]); // floyd + do_test(10, 6, &[8, 3, 5, 9, 0, 6]); // floyd + do_test(25, 10, &[18, 14, 9, 15, 0, 13, 5, 24]); // floyd do_test(300, 8, &[30, 283, 150, 1, 73, 13, 285, 35]); // floyd do_test(300, 80, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace do_test(300, 180, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 24c65bc9f08..a61e516924c 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -725,7 +725,7 @@ mod test { .choose_multiple(&mut r, 8) .cloned() .collect::>(), - &['d', 'm', 'b', 'n', 'c', 'k', 'h', 'e'] + &['d', 'm', 'n', 'k', 'h', 'e', 'b', 'c'] ); #[cfg(feature = "alloc")] From 3107a54aea87ee857441027c2f33c4608e2a40dc Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 4 Jan 2023 19:49:17 +0200 Subject: [PATCH 278/443] Relax `Sized` bound on `Distribution for &D` (#1278) --- src/distributions/distribution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index c5cf6a607b4..4fcb4c3246d 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -112,7 +112,7 @@ pub trait Distribution { } } -impl<'a, T, D: Distribution> Distribution for &'a D { +impl<'a, T, D: Distribution + ?Sized> Distribution for &'a D { fn sample(&self, rng: &mut R) -> T { (*self).sample(rng) } From 1e96eb4593717181d435f5895ba5ed63a58a5efa Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 5 Jan 2023 10:49:33 +0000 Subject: [PATCH 279/443] Added new versions of choose and choose_stable (#1268) * Added new versions of choose and choose_stable * Removed coin_flipper tests which were unnecessary and not building on ci * Performance optimizations in coin_flipper * Clippy fixes and more documentation * Added a correctness fix for coin_flipper * Update benches/seq.rs Co-authored-by: Vinzent Steinberg * Update benches/seq.rs Co-authored-by: Vinzent Steinberg * Removed old version of choose and choose stable and updated value stability tests * Moved sequence choose benchmarks to their own file * Reworked coin_flipper * Use criterion for seq_choose benches * Removed an old comment * Change how c is estimated in coin_flipper * Revert "Use criterion for seq_choose benches" This reverts commit 23395391370ab95694558be90686eb16494e590a. * Added seq_choose benches for smaller numbers * Removed some unneeded lines from seq_choose * Improvements in coin_flipper.rs * Small refactor of coin_flipper * Tidied comments in coin_flipper * Use criterion for seq_choose benchmarks * Made choose not generate a random number if len=1 * small change to IteratorRandom::choose * Made it easier to change seq_choose benchmarks RNG * Added Pcg64 benchmarks for seq_choose * Added TODO to coin_flipper * Changed criterion settings in seq_choose Co-authored-by: Vinzent Steinberg --- Cargo.toml | 5 + benches/seq.rs | 72 +--------- benches/seq_choose.rs | 111 +++++++++++++++ src/seq/coin_flipper.rs | 152 ++++++++++++++++++++ src/seq/mod.rs | 305 +++++++++++++++++++++++++--------------- 5 files changed, 460 insertions(+), 185 deletions(-) create mode 100644 benches/seq_choose.rs create mode 100644 src/seq/coin_flipper.rs diff --git a/Cargo.toml b/Cargo.toml index da251903dd8..19f7573851c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,3 +75,8 @@ rand_pcg = { path = "rand_pcg", version = "0.4.0" } bincode = "1.2.1" rayon = "1.5.3" criterion = { version = "0.4" } + +[[bench]] +name = "seq_choose" +path = "benches/seq_choose.rs" +harness = false \ No newline at end of file diff --git a/benches/seq.rs b/benches/seq.rs index 5b3a846f60b..3d57d4872e6 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -13,9 +13,9 @@ extern crate test; use test::Bencher; +use core::mem::size_of; use rand::prelude::*; use rand::seq::*; -use core::mem::size_of; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. @@ -74,76 +74,6 @@ seq_slice_choose_multiple!(seq_slice_choose_multiple_950_of_1000, 950, 1000); seq_slice_choose_multiple!(seq_slice_choose_multiple_10_of_100, 10, 100); seq_slice_choose_multiple!(seq_slice_choose_multiple_90_of_100, 90, 100); -#[bench] -fn seq_iter_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &mut [usize] = &mut [1; 1000]; - for (i, r) in x.iter_mut().enumerate() { - *r = i; - } - b.iter(|| { - let mut s = 0; - for _ in 0..RAND_BENCH_N { - s += x.iter().choose(&mut rng).unwrap(); - } - s - }); - b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; -} - -#[derive(Clone)] -struct UnhintedIterator { - iter: I, -} -impl Iterator for UnhintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } -} - -#[derive(Clone)] -struct WindowHintedIterator { - iter: I, - window_size: usize, -} -impl Iterator for WindowHintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - (core::cmp::min(self.iter.len(), self.window_size), None) - } -} - -#[bench] -fn seq_iter_unhinted_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; 1000]; - b.iter(|| { - UnhintedIterator { iter: x.iter() } - .choose(&mut rng) - .unwrap() - }) -} - -#[bench] -fn seq_iter_window_hinted_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; 1000]; - b.iter(|| { - WindowHintedIterator { - iter: x.iter(), - window_size: 7, - } - .choose(&mut rng) - }) -} - #[bench] fn seq_iter_choose_multiple_10_of_100(b: &mut Bencher) { let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs new file mode 100644 index 00000000000..44b4bdf9724 --- /dev/null +++ b/benches/seq_choose.rs @@ -0,0 +1,111 @@ +// Copyright 2018-2022 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::prelude::*; +use rand::SeedableRng; + +criterion_group!( +name = benches; +config = Criterion::default(); +targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + bench_rng::(c, "ChaCha20"); + bench_rng::(c, "Pcg32"); + bench_rng::(c, "Pcg64"); +} + +fn bench_rng(c: &mut Criterion, rng_name: &'static str) { + for length in [1, 2, 3, 10, 100, 1000].map(|x| black_box(x)) { + c.bench_function( + format!("choose_size-hinted_from_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_size_hinted(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_stable_from_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_stable(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_unhinted_from_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_unhinted(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_windowed_from_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_windowed(length, 7, &mut rng)) + }, + ); + } +} + +fn choose_size_hinted(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose(rng) +} + +fn choose_stable(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose_stable(rng) +} + +fn choose_unhinted(max: usize, rng: &mut R) -> Option { + let iterator = UnhintedIterator { iter: (0..max) }; + iterator.choose(rng) +} + +fn choose_windowed(max: usize, window_size: usize, rng: &mut R) -> Option { + let iterator = WindowHintedIterator { + iter: (0..max), + window_size, + }; + iterator.choose(rng) +} + +#[derive(Clone)] +struct UnhintedIterator { + iter: I, +} +impl Iterator for UnhintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +#[derive(Clone)] +struct WindowHintedIterator { + iter: I, + window_size: usize, +} +impl Iterator for WindowHintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + (core::cmp::min(self.iter.len(), self.window_size), None) + } +} diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs new file mode 100644 index 00000000000..77c18ded432 --- /dev/null +++ b/src/seq/coin_flipper.rs @@ -0,0 +1,152 @@ +use crate::RngCore; + +pub(crate) struct CoinFlipper { + pub rng: R, + chunk: u32, //TODO(opt): this should depend on RNG word size + chunk_remaining: u32, +} + +impl CoinFlipper { + pub fn new(rng: R) -> Self { + Self { + rng, + chunk: 0, + chunk_remaining: 0, + } + } + + #[inline] + /// Returns true with a probability of 1 / d + /// Uses an expected two bits of randomness + /// Panics if d == 0 + pub fn gen_ratio_one_over(&mut self, d: usize) -> bool { + debug_assert_ne!(d, 0); + // This uses the same logic as `gen_ratio` but is optimized for the case that + // the starting numerator is one (which it always is for `Sequence::Choose()`) + + // In this case (but not `gen_ratio`), this way of calculating c is always accurate + let c = (usize::BITS - 1 - d.leading_zeros()).min(32); + + if self.flip_c_heads(c) { + let numerator = 1 << c; + return self.gen_ratio(numerator, d); + } else { + return false; + } + } + + #[inline] + /// Returns true with a probability of n / d + /// Uses an expected two bits of randomness + fn gen_ratio(&mut self, mut n: usize, d: usize) -> bool { + // Explanation: + // We are trying to return true with a probability of n / d + // If n >= d, we can just return true + // Otherwise there are two possibilities 2n < d and 2n >= d + // In either case we flip a coin. + // If 2n < d + // If it comes up tails, return false + // If it comes up heads, double n and start again + // This is fair because (0.5 * 0) + (0.5 * 2n / d) = n / d and 2n is less than d + // (if 2n was greater than d we would effectively round it down to 1 + // by returning true) + // If 2n >= d + // If it comes up tails, set n to 2n - d and start again + // If it comes up heads, return true + // This is fair because (0.5 * 1) + (0.5 * (2n - d) / d) = n / d + // Note that if 2n = d and the coin comes up tails, n will be set to 0 + // before restarting which is equivalent to returning false. + + // As a performance optimization we can flip multiple coins at once + // This is efficient because we can use the `lzcnt` intrinsic + // We can check up to 32 flips at once but we only receive one bit of information + // - all heads or at least one tail. + + // Let c be the number of coins to flip. 1 <= c <= 32 + // If 2n < d, n * 2^c < d + // If the result is all heads, then set n to n * 2^c + // If there was at least one tail, return false + // If 2n >= d, the order of results matters so we flip one coin at a time so c = 1 + // Ideally, c will be as high as possible within these constraints + + while n < d { + // Find a good value for c by counting leading zeros + // This will either give the highest possible c, or 1 less than that + let c = n + .leading_zeros() + .saturating_sub(d.leading_zeros() + 1) + .clamp(1, 32); + + if self.flip_c_heads(c) { + // All heads + // Set n to n * 2^c + // If 2n >= d, the while loop will exit and we will return `true` + // If n * 2^c > `usize::MAX` we always return `true` anyway + n = n.saturating_mul(2_usize.pow(c)); + } else { + //At least one tail + if c == 1 { + // Calculate 2n - d. + // We need to use wrapping as 2n might be greater than `usize::MAX` + let next_n = n.wrapping_add(n).wrapping_sub(d); + if next_n == 0 || next_n > n { + // This will happen if 2n < d + return false; + } + n = next_n; + } else { + // c > 1 so 2n < d so we can return false + return false; + } + } + } + true + } + + /// If the next `c` bits of randomness all represent heads, consume them, return true + /// Otherwise return false and consume the number of heads plus one. + /// Generates new bits of randomness when necessary (in 32 bit chunks) + /// Has a 1 in 2 to the `c` chance of returning true + /// `c` must be less than or equal to 32 + fn flip_c_heads(&mut self, mut c: u32) -> bool { + debug_assert!(c <= 32); + // Note that zeros on the left of the chunk represent heads. + // It needs to be this way round because zeros are filled in when left shifting + loop { + let zeros = self.chunk.leading_zeros(); + + if zeros < c { + // The happy path - we found a 1 and can return false + // Note that because a 1 bit was detected, + // We cannot have run out of random bits so we don't need to check + + // First consume all of the bits read + // Using shl seems to give worse performance for size-hinted iterators + self.chunk = self.chunk.wrapping_shl(zeros + 1); + + self.chunk_remaining = self.chunk_remaining.saturating_sub(zeros + 1); + return false; + } else { + // The number of zeros is larger than `c` + // There are two possibilities + if let Some(new_remaining) = self.chunk_remaining.checked_sub(c) { + // Those zeroes were all part of our random chunk, + // throw away `c` bits of randomness and return true + self.chunk_remaining = new_remaining; + self.chunk <<= c; + return true; + } else { + // Some of those zeroes were part of the random chunk + // and some were part of the space behind it + // We need to take into account only the zeroes that were random + c -= self.chunk_remaining; + + // Generate a new chunk + self.chunk = self.rng.next_u32(); + self.chunk_remaining = 32; + // Go back to start of loop + } + } + } + } +} diff --git a/src/seq/mod.rs b/src/seq/mod.rs index a61e516924c..e1286105c5a 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -24,20 +24,25 @@ //! `usize` indices are sampled as a `u32` where possible (also providing a //! small performance boost in some cases). - +mod coin_flipper; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod index; -#[cfg(feature = "alloc")] use core::ops::Index; +#[cfg(feature = "alloc")] +use core::ops::Index; -#[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; #[cfg(feature = "alloc")] use crate::distributions::uniform::{SampleBorrow, SampleUniform}; -#[cfg(feature = "alloc")] use crate::distributions::WeightedError; +#[cfg(feature = "alloc")] +use crate::distributions::WeightedError; use crate::Rng; +use self::coin_flipper::CoinFlipper; + /// Extension trait on slices, providing random mutation and sampling methods. /// /// This trait is implemented on all `[T]` slice types, providing several @@ -77,14 +82,16 @@ pub trait SliceRandom { /// assert_eq!(choices[..0].choose(&mut rng), None); /// ``` fn choose(&self, rng: &mut R) -> Option<&Self::Item> - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Returns a mutable reference to one random element of the slice, or /// `None` if the slice is empty. /// /// For slices, complexity is `O(1)`. fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Chooses `amount` elements from the slice at random, without repetition, /// and in random order. The returned iterator is appropriate both for @@ -113,7 +120,8 @@ pub trait SliceRandom { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Similar to [`choose`], but where the likelihood of each outcome may be /// specified. @@ -249,7 +257,8 @@ pub trait SliceRandom { /// println!("Shuffled: {:?}", y); /// ``` fn shuffle(&mut self, rng: &mut R) - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Shuffle a slice in place, but exit early. /// @@ -271,7 +280,8 @@ pub trait SliceRandom { fn partial_shuffle( &mut self, rng: &mut R, amount: usize, ) -> (&mut [Self::Item], &mut [Self::Item]) - where R: Rng + ?Sized; + where + R: Rng + ?Sized; } /// Extension trait on iterators, providing random sampling methods. @@ -309,26 +319,30 @@ pub trait IteratorRandom: Iterator + Sized { /// `choose` returning different elements. If you want consistent results /// and RNG usage consider using [`IteratorRandom::choose_stable`]. fn choose(mut self, rng: &mut R) -> Option - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let (mut lower, mut upper) = self.size_hint(); - let mut consumed = 0; let mut result = None; // Handling for this condition outside the loop allows the optimizer to eliminate the loop // when the Iterator is an ExactSizeIterator. This has a large performance impact on e.g. // seq_iter_choose_from_1000. if upper == Some(lower) { - return if lower == 0 { - None - } else { - self.nth(gen_index(rng, lower)) + return match lower { + 0 => None, + 1 => self.next(), + _ => self.nth(gen_index(rng, lower)), }; } + let mut coin_flipper = coin_flipper::CoinFlipper::new(rng); + let mut consumed = 0; + // Continue until the iterator is exhausted loop { if lower > 1 { - let ix = gen_index(rng, lower + consumed); + let ix = gen_index(coin_flipper.rng, lower + consumed); let skip = if ix < lower { result = self.nth(ix); lower - (ix + 1) @@ -348,7 +362,7 @@ pub trait IteratorRandom: Iterator + Sized { return result; } consumed += 1; - if gen_index(rng, consumed) == 0 { + if coin_flipper.gen_ratio_one_over(consumed) { result = elem; } } @@ -378,9 +392,12 @@ pub trait IteratorRandom: Iterator + Sized { /// /// [`choose`]: IteratorRandom::choose fn choose_stable(mut self, rng: &mut R) -> Option - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let mut consumed = 0; let mut result = None; + let mut coin_flipper = CoinFlipper::new(rng); loop { // Currently the only way to skip elements is `nth()`. So we need to @@ -392,7 +409,7 @@ pub trait IteratorRandom: Iterator + Sized { let (lower, _) = self.size_hint(); if lower >= 2 { let highest_selected = (0..lower) - .filter(|ix| gen_index(rng, consumed+ix+1) == 0) + .filter(|ix| coin_flipper.gen_ratio_one_over(consumed + ix + 1)) .last(); consumed += lower; @@ -407,10 +424,10 @@ pub trait IteratorRandom: Iterator + Sized { let elem = self.nth(next); if elem.is_none() { - return result + return result; } - if gen_index(rng, consumed+1) == 0 { + if coin_flipper.gen_ratio_one_over(consumed + 1) { result = elem; } consumed += 1; @@ -431,7 +448,9 @@ pub trait IteratorRandom: Iterator + Sized { /// Complexity is `O(n)` where `n` is the length of the iterator. /// For slices, prefer [`SliceRandom::choose_multiple`]. fn choose_multiple_fill(mut self, rng: &mut R, buf: &mut [Self::Item]) -> usize - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let amount = buf.len(); let mut len = 0; while len < amount { @@ -471,7 +490,9 @@ pub trait IteratorRandom: Iterator + Sized { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(mut self, rng: &mut R, amount: usize) -> Vec - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let mut reservoir = Vec::with_capacity(amount); reservoir.extend(self.by_ref().take(amount)); @@ -495,12 +516,13 @@ pub trait IteratorRandom: Iterator + Sized { } } - impl SliceRandom for [T] { type Item = T; fn choose(&self, rng: &mut R) -> Option<&Self::Item> - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { if self.is_empty() { None } else { @@ -509,7 +531,9 @@ impl SliceRandom for [T] { } fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { if self.is_empty() { None } else { @@ -520,7 +544,9 @@ impl SliceRandom for [T] { #[cfg(feature = "alloc")] fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let amount = ::core::cmp::min(amount, self.len()); SliceChooseIter { slice: self, @@ -591,7 +617,9 @@ impl SliceRandom for [T] { } fn shuffle(&mut self, rng: &mut R) - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { for i in (1..self.len()).rev() { // invariant: elements with index > i have been locked in place. self.swap(i, gen_index(rng, i + 1)); @@ -601,7 +629,9 @@ impl SliceRandom for [T] { fn partial_shuffle( &mut self, rng: &mut R, amount: usize, ) -> (&mut [Self::Item], &mut [Self::Item]) - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { // This applies Durstenfeld's algorithm for the // [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) // for an unbiased permutation, but exits early after choosing `amount` @@ -621,7 +651,6 @@ impl SliceRandom for [T] { impl IteratorRandom for I where I: Iterator + Sized {} - /// An iterator over multiple slice elements. /// /// This struct is created by @@ -658,12 +687,12 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator } } - // Sample a number uniformly between 0 and `ubound`. Uses 32-bit sampling where // possible, primarily in order to produce the same output on 32-bit and 64-bit // platforms. #[inline] fn gen_index(rng: &mut R, ubound: usize) -> usize { + if ubound <= (core::u32::MAX as usize) { rng.gen_range(0..ubound as u32) as usize } else { @@ -671,12 +700,13 @@ fn gen_index(rng: &mut R, ubound: usize) -> usize { } } - #[cfg(test)] mod test { use super::*; - #[cfg(feature = "alloc")] use crate::Rng; - #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; + #[cfg(feature = "alloc")] + use crate::Rng; + #[cfg(all(feature = "alloc", not(feature = "std")))] + use alloc::vec::Vec; #[test] fn test_slice_choose() { @@ -837,28 +867,40 @@ mod test { #[cfg(feature = "alloc")] test_iter(r, (0..9).collect::>().into_iter()); test_iter(r, UnhintedIterator { iter: 0..9 }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }, + ); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }, + ); assert_eq!((0..0).choose(r), None); assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); @@ -891,28 +933,40 @@ mod test { #[cfg(feature = "alloc")] test_iter(r, (0..9).collect::>().into_iter()); test_iter(r, UnhintedIterator { iter: 0..9 }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }, + ); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }, + ); assert_eq!((0..0).choose(r), None); assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); @@ -932,33 +986,48 @@ mod test { } let reference = test_iter(0..9); - assert_eq!(test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), reference); + assert_eq!( + test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), + reference + ); #[cfg(feature = "alloc")] assert_eq!(test_iter((0..9).collect::>().into_iter()), reference); assert_eq!(test_iter(UnhintedIterator { iter: 0..9 }), reference); - assert_eq!(test_iter(ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }), reference); - assert_eq!(test_iter(ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }), reference); - assert_eq!(test_iter(WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }), reference); - assert_eq!(test_iter(WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }), reference); + assert_eq!( + test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }), + reference + ); + assert_eq!( + test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }), + reference + ); + assert_eq!( + test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }), + reference + ); + assert_eq!( + test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }), + reference + ); } #[test] @@ -1129,7 +1198,7 @@ mod test { assert_eq!(choose([].iter().cloned()), None); assert_eq!(choose(0..100), Some(33)); - assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(40)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, @@ -1174,8 +1243,8 @@ mod test { } assert_eq!(choose([].iter().cloned()), None); - assert_eq!(choose(0..100), Some(40)); - assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(40)); + assert_eq!(choose(0..100), Some(27)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, @@ -1183,7 +1252,7 @@ mod test { chunk_remaining: 32, hint_total_size: false, }), - Some(40) + Some(27) ); assert_eq!( choose(ChunkHintedIterator { @@ -1192,7 +1261,7 @@ mod test { chunk_remaining: 32, hint_total_size: true, }), - Some(40) + Some(27) ); assert_eq!( choose(WindowHintedIterator { @@ -1200,7 +1269,7 @@ mod test { window_size: 32, hint_total_size: false, }), - Some(40) + Some(27) ); assert_eq!( choose(WindowHintedIterator { @@ -1208,7 +1277,7 @@ mod test { window_size: 32, hint_total_size: true, }), - Some(40) + Some(27) ); } @@ -1260,9 +1329,13 @@ mod test { // Case 2: All of the weights are 0 let choices = [('a', 0), ('b', 0), ('c', 0)]; - assert_eq!(choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap().count(), 2); + assert_eq!( + choices + .choose_multiple_weighted(&mut rng, 2, |item| item.1) + .unwrap() + .count(), + 2 + ); // Case 3: Negative weights let choices = [('a', -1), ('b', 1), ('c', 1)]; @@ -1275,9 +1348,13 @@ mod test { // Case 4: Empty list let choices = []; - assert_eq!(choices - .choose_multiple_weighted(&mut rng, 0, |_: &()| 0) - .unwrap().count(), 0); + assert_eq!( + choices + .choose_multiple_weighted(&mut rng, 0, |_: &()| 0) + .unwrap() + .count(), + 0 + ); // Case 5: NaN weights let choices = [('a', core::f64::NAN), ('b', 1.0), ('c', 1.0)]; From 4bde8a0adb517ec956fcec91665922f6360f974b Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Sun, 8 Jan 2023 16:14:26 +0000 Subject: [PATCH 280/443] Performance improvements for `shuffle` and `partial_shuffle` (#1272) * Made shuffle and partial_shuffle faster * Use criterion benchmarks for shuffle * Added a note about RNG word size * Tidied comments * Added a debug_assert * Added a comment re possible further optimization * Added and updated copyright notices * Revert cfg mistake * Reverted change to mod.rs * Removed ChaCha20 benches from shuffle * moved debug_assert out of a const fn --- Cargo.toml | 5 ++ benches/seq_choose.rs | 2 +- benches/shuffle.rs | 50 ++++++++++++++++ src/seq/coin_flipper.rs | 8 +++ src/seq/increasing_uniform.rs | 108 ++++++++++++++++++++++++++++++++++ src/seq/mod.rs | 51 ++++++++++------ 6 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 benches/shuffle.rs create mode 100644 src/seq/increasing_uniform.rs diff --git a/Cargo.toml b/Cargo.toml index 19f7573851c..ec0e4d77676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,4 +79,9 @@ criterion = { version = "0.4" } [[bench]] name = "seq_choose" path = "benches/seq_choose.rs" +harness = false + +[[bench]] +name = "shuffle" +path = "benches/shuffle.rs" harness = false \ No newline at end of file diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 44b4bdf9724..2c34d77ced7 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -1,4 +1,4 @@ -// Copyright 2018-2022 Developers of the Rand project. +// Copyright 2018-2023 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license diff --git a/benches/shuffle.rs b/benches/shuffle.rs new file mode 100644 index 00000000000..3d6878219f7 --- /dev/null +++ b/benches/shuffle.rs @@ -0,0 +1,50 @@ +// Copyright 2018-2023 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::prelude::*; +use rand::SeedableRng; + +criterion_group!( +name = benches; +config = Criterion::default(); +targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + bench_rng::(c, "ChaCha12"); + bench_rng::(c, "Pcg32"); + bench_rng::(c, "Pcg64"); +} + +fn bench_rng(c: &mut Criterion, rng_name: &'static str) { + for length in [1, 2, 3, 10, 100, 1000, 10000].map(|x| black_box(x)) { + c.bench_function(format!("shuffle_{length}_{rng_name}").as_str(), |b| { + let mut rng = Rng::seed_from_u64(123); + let mut vec: Vec = (0..length).collect(); + b.iter(|| { + vec.shuffle(&mut rng); + vec[0] + }) + }); + + if length >= 10 { + c.bench_function( + format!("partial_shuffle_{length}_{rng_name}").as_str(), + |b| { + let mut rng = Rng::seed_from_u64(123); + let mut vec: Vec = (0..length).collect(); + b.iter(|| { + vec.partial_shuffle(&mut rng, length / 2); + vec[0] + }) + }, + ); + } + } +} diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 77c18ded432..05f18d71b2e 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -1,3 +1,11 @@ +// Copyright 2018-2023 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + use crate::RngCore; pub(crate) struct CoinFlipper { diff --git a/src/seq/increasing_uniform.rs b/src/seq/increasing_uniform.rs new file mode 100644 index 00000000000..3208c656fb5 --- /dev/null +++ b/src/seq/increasing_uniform.rs @@ -0,0 +1,108 @@ +// Copyright 2018-2023 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::{Rng, RngCore}; + +/// Similar to a Uniform distribution, +/// but after returning a number in the range [0,n], n is increased by 1. +pub(crate) struct IncreasingUniform { + pub rng: R, + n: u32, + // Chunk is a random number in [0, (n + 1) * (n + 2) *..* (n + chunk_remaining) ) + chunk: u32, + chunk_remaining: u8, +} + +impl IncreasingUniform { + /// Create a dice roller. + /// The next item returned will be a random number in the range [0,n] + pub fn new(rng: R, n: u32) -> Self { + // If n = 0, the first number returned will always be 0 + // so we don't need to generate a random number + let chunk_remaining = if n == 0 { 1 } else { 0 }; + Self { + rng, + n, + chunk: 0, + chunk_remaining, + } + } + + /// Returns a number in [0,n] and increments n by 1. + /// Generates new random bits as needed + /// Panics if `n >= u32::MAX` + #[inline] + pub fn next_index(&mut self) -> usize { + let next_n = self.n + 1; + + // There's room for further optimisation here: + // gen_range uses rejection sampling (or other method; see #1196) to avoid bias. + // When the initial sample is biased for range 0..bound + // it may still be viable to use for a smaller bound + // (especially if small biases are considered acceptable). + + let next_chunk_remaining = self.chunk_remaining.checked_sub(1).unwrap_or_else(|| { + // If the chunk is empty, generate a new chunk + let (bound, remaining) = calculate_bound_u32(next_n); + // bound = (n + 1) * (n + 2) *..* (n + remaining) + self.chunk = self.rng.gen_range(0..bound); + // Chunk is a random number in + // [0, (n + 1) * (n + 2) *..* (n + remaining) ) + + remaining - 1 + }); + + let result = if next_chunk_remaining == 0 { + // `chunk` is a random number in the range [0..n+1) + // Because `chunk_remaining` is about to be set to zero + // we do not need to clear the chunk here + self.chunk as usize + } else { + // `chunk` is a random number in a range that is a multiple of n+1 + // so r will be a random number in [0..n+1) + let r = self.chunk % next_n; + self.chunk /= next_n; + r as usize + }; + + self.chunk_remaining = next_chunk_remaining; + self.n = next_n; + result + } +} + +#[inline] +/// Calculates `bound`, `count` such that bound (m)*(m+1)*..*(m + remaining - 1) +fn calculate_bound_u32(m: u32) -> (u32, u8) { + debug_assert!(m > 0); + #[inline] + const fn inner(m: u32) -> (u32, u8) { + let mut product = m; + let mut current = m + 1; + + loop { + if let Some(p) = u32::checked_mul(product, current) { + product = p; + current += 1; + } else { + // Count has a maximum value of 13 for when min is 1 or 2 + let count = (current - m) as u8; + return (product, count); + } + } + } + + const RESULT2: (u32, u8) = inner(2); + if m == 2 { + // Making this value a constant instead of recalculating it + // gives a significant (~50%) performance boost for small shuffles + return RESULT2; + } + + inner(m) +} diff --git a/src/seq/mod.rs b/src/seq/mod.rs index e1286105c5a..d9b38e920d7 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Developers of the Rand project. +// Copyright 2018-2023 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -29,6 +29,8 @@ mod coin_flipper; #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod index; +mod increasing_uniform; + #[cfg(feature = "alloc")] use core::ops::Index; @@ -42,6 +44,7 @@ use crate::distributions::WeightedError; use crate::Rng; use self::coin_flipper::CoinFlipper; +use self::increasing_uniform::IncreasingUniform; /// Extension trait on slices, providing random mutation and sampling methods. /// @@ -620,10 +623,11 @@ impl SliceRandom for [T] { where R: Rng + ?Sized, { - for i in (1..self.len()).rev() { - // invariant: elements with index > i have been locked in place. - self.swap(i, gen_index(rng, i + 1)); + if self.len() <= 1 { + // There is no need to shuffle an empty or single element slice + return; } + self.partial_shuffle(rng, self.len()); } fn partial_shuffle( @@ -632,19 +636,30 @@ impl SliceRandom for [T] { where R: Rng + ?Sized, { - // This applies Durstenfeld's algorithm for the - // [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) - // for an unbiased permutation, but exits early after choosing `amount` - // elements. - - let len = self.len(); - let end = if amount >= len { 0 } else { len - amount }; + let m = self.len().saturating_sub(amount); - for i in (end..len).rev() { - // invariant: elements with index > i have been locked in place. - self.swap(i, gen_index(rng, i + 1)); + // The algorithm below is based on Durstenfeld's algorithm for the + // [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) + // for an unbiased permutation. + // It ensures that the last `amount` elements of the slice + // are randomly selected from the whole slice. + + //`IncreasingUniform::next_index()` is faster than `gen_index` + //but only works for 32 bit integers + //So we must use the slow method if the slice is longer than that. + if self.len() < (u32::MAX as usize) { + let mut chooser = IncreasingUniform::new(rng, m as u32); + for i in m..self.len() { + let index = chooser.next_index(); + self.swap(i, index); + } + } else { + for i in m..self.len() { + let index = gen_index(rng, i + 1); + self.swap(i, index); + } } - let r = self.split_at_mut(end); + let r = self.split_at_mut(m); (r.1, r.0) } } @@ -765,11 +780,11 @@ mod test { let mut r = crate::test::rng(414); nums.shuffle(&mut r); - assert_eq!(nums, [9, 5, 3, 10, 7, 12, 8, 11, 6, 4, 0, 2, 1]); + assert_eq!(nums, [5, 11, 0, 8, 7, 12, 6, 4, 9, 3, 1, 2, 10]); nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; let res = nums.partial_shuffle(&mut r, 6); - assert_eq!(res.0, &mut [7, 4, 8, 6, 9, 3]); - assert_eq!(res.1, &mut [0, 1, 2, 12, 11, 5, 10]); + assert_eq!(res.0, &mut [7, 12, 6, 8, 1, 9]); + assert_eq!(res.1, &mut [0, 11, 2, 3, 4, 5, 10]); } #[derive(Clone)] From ae4b48ece870c70cee5143888656711803c87749 Mon Sep 17 00:00:00 2001 From: Arya Date: Thu, 2 Feb 2023 04:30:08 -0500 Subject: [PATCH 281/443] Add note about floating point weights in update_weights docs (#1280) --- src/distributions/weighted_index.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index b1b2071abc1..0131ed157c0 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -141,6 +141,10 @@ impl WeightedIndex { /// allocation internally. /// /// In case of error, `self` is not modified. + /// + /// Note: Updating floating-point weights may cause slight inaccuracies in the total weight. + /// This method may not return `WeightedError::AllWeightsZero` when all weights + /// are zero if using floating-point weights. pub fn update_weights(&mut self, new_weights: &[(usize, &X)]) -> Result<(), WeightedError> where X: for<'a> ::core::ops::AddAssign<&'a X> + for<'a> ::core::ops::SubAssign<&'a X> From 7d73990096890960dbc086e5ad93c453e4435b25 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 6 Feb 2023 11:55:22 +0100 Subject: [PATCH 282/443] Make `Uniform` constructors return a result (#1229) * Forbid unsafe code in crates without unsafe code This helps tools like `cargo geiger`. * Make `Uniform` constructors return a result - This is a breaking change. - The new error type had to be made public, otherwise `Uniform` could not be extended for user-defined types by implementing `UniformSampler`. - `rand_distr` was updated accordingly. - Also forbid unsafe code for crates where none is used. Fixes #1195, #1211. * Address review feedback * Make `sample_single` return a `Result` * Fix benchmarks * Small fixes * Update src/distributions/uniform.rs --- CHANGELOG.md | 5 +- benches/distributions.rs | 38 +-- examples/monte-carlo.rs | 2 +- examples/monty-hall.rs | 2 +- examples/rayon-monte-carlo.rs | 2 +- rand_chacha/src/lib.rs | 1 + rand_distr/CHANGELOG.md | 1 + rand_distr/src/binomial.rs | 4 +- rand_distr/src/hypergeometric.rs | 2 +- rand_distr/src/lib.rs | 1 + rand_distr/src/unit_ball.rs | 2 +- rand_distr/src/unit_circle.rs | 2 +- rand_distr/src/unit_disc.rs | 2 +- rand_distr/src/unit_sphere.rs | 2 +- rand_distr/src/weighted_alias.rs | 6 +- rand_pcg/src/lib.rs | 1 + src/distributions/distribution.rs | 7 +- src/distributions/other.rs | 4 +- src/distributions/slice.rs | 2 +- src/distributions/uniform.rs | 388 +++++++++++++++------------- src/distributions/weighted_index.rs | 5 +- src/rng.rs | 8 +- src/seq/index.rs | 2 +- 23 files changed, 261 insertions(+), 228 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b27a5edb493..083b7dbbeb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,10 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. -## [Unreleased API changing release] +## [0.9.0] - unreleased +### Distributions +- `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229) +- `Uniform` implements `TryFrom` instead of `From` for ranges (#1229) ### Other - Simpler and faster implementation of Floyd's F2 (#1277). This diff --git a/benches/distributions.rs b/benches/distributions.rs index 76d5d258d9d..f637fe4ae47 100644 --- a/benches/distributions.rs +++ b/benches/distributions.rs @@ -131,36 +131,36 @@ macro_rules! distr { } // uniform -distr_int!(distr_uniform_i8, i8, Uniform::new(20i8, 100)); -distr_int!(distr_uniform_i16, i16, Uniform::new(-500i16, 2000)); -distr_int!(distr_uniform_i32, i32, Uniform::new(-200_000_000i32, 800_000_000)); -distr_int!(distr_uniform_i64, i64, Uniform::new(3i64, 123_456_789_123)); -distr_int!(distr_uniform_i128, i128, Uniform::new(-123_456_789_123i128, 123_456_789_123_456_789)); -distr_int!(distr_uniform_usize16, usize, Uniform::new(0usize, 0xb9d7)); -distr_int!(distr_uniform_usize32, usize, Uniform::new(0usize, 0x548c0f43)); +distr_int!(distr_uniform_i8, i8, Uniform::new(20i8, 100).unwrap()); +distr_int!(distr_uniform_i16, i16, Uniform::new(-500i16, 2000).unwrap()); +distr_int!(distr_uniform_i32, i32, Uniform::new(-200_000_000i32, 800_000_000).unwrap()); +distr_int!(distr_uniform_i64, i64, Uniform::new(3i64, 123_456_789_123).unwrap()); +distr_int!(distr_uniform_i128, i128, Uniform::new(-123_456_789_123i128, 123_456_789_123_456_789).unwrap()); +distr_int!(distr_uniform_usize16, usize, Uniform::new(0usize, 0xb9d7).unwrap()); +distr_int!(distr_uniform_usize32, usize, Uniform::new(0usize, 0x548c0f43).unwrap()); #[cfg(target_pointer_width = "64")] -distr_int!(distr_uniform_usize64, usize, Uniform::new(0usize, 0x3a42714f2bf927a8)); -distr_int!(distr_uniform_isize, isize, Uniform::new(-1060478432isize, 1858574057)); +distr_int!(distr_uniform_usize64, usize, Uniform::new(0usize, 0x3a42714f2bf927a8).unwrap()); +distr_int!(distr_uniform_isize, isize, Uniform::new(-1060478432isize, 1858574057).unwrap()); -distr_float!(distr_uniform_f32, f32, Uniform::new(2.26f32, 2.319)); -distr_float!(distr_uniform_f64, f64, Uniform::new(2.26f64, 2.319)); +distr_float!(distr_uniform_f32, f32, Uniform::new(2.26f32, 2.319).unwrap()); +distr_float!(distr_uniform_f64, f64, Uniform::new(2.26f64, 2.319).unwrap()); const LARGE_SEC: u64 = u64::max_value() / 1000; distr_duration!(distr_uniform_duration_largest, - Uniform::new_inclusive(Duration::new(0, 0), Duration::new(u64::max_value(), 999_999_999)) + Uniform::new_inclusive(Duration::new(0, 0), Duration::new(u64::max_value(), 999_999_999)).unwrap() ); distr_duration!(distr_uniform_duration_large, - Uniform::new(Duration::new(0, 0), Duration::new(LARGE_SEC, 1_000_000_000 / 2)) + Uniform::new(Duration::new(0, 0), Duration::new(LARGE_SEC, 1_000_000_000 / 2)).unwrap() ); distr_duration!(distr_uniform_duration_one, - Uniform::new(Duration::new(0, 0), Duration::new(1, 0)) + Uniform::new(Duration::new(0, 0), Duration::new(1, 0)).unwrap() ); distr_duration!(distr_uniform_duration_variety, - Uniform::new(Duration::new(10000, 423423), Duration::new(200000, 6969954)) + Uniform::new(Duration::new(10000, 423423), Duration::new(200000, 6969954)).unwrap() ); distr_duration!(distr_uniform_duration_edge, - Uniform::new_inclusive(Duration::new(LARGE_SEC, 999_999_999), Duration::new(LARGE_SEC + 1, 1)) + Uniform::new_inclusive(Duration::new(LARGE_SEC, 999_999_999), Duration::new(LARGE_SEC + 1, 1)).unwrap() ); // standard @@ -272,7 +272,7 @@ macro_rules! uniform_sample { let high = black_box($high); b.iter(|| { for _ in 0..10 { - let dist = UniformInt::<$type>::new(low, high); + let dist = UniformInt::<$type>::new(low, high).unwrap(); for _ in 0..$count { black_box(dist.sample(&mut rng)); } @@ -291,7 +291,7 @@ macro_rules! uniform_inclusive { let high = black_box($high); b.iter(|| { for _ in 0..10 { - let dist = UniformInt::<$type>::new_inclusive(low, high); + let dist = UniformInt::<$type>::new_inclusive(low, high).unwrap(); for _ in 0..$count { black_box(dist.sample(&mut rng)); } @@ -311,7 +311,7 @@ macro_rules! uniform_single { let high = black_box($high); b.iter(|| { for _ in 0..(10 * $count) { - black_box(UniformInt::<$type>::sample_single(low, high, &mut rng)); + black_box(UniformInt::<$type>::sample_single(low, high, &mut rng).unwrap()); } }); } diff --git a/examples/monte-carlo.rs b/examples/monte-carlo.rs index 6cc9f4e142a..a72cc1e9f47 100644 --- a/examples/monte-carlo.rs +++ b/examples/monte-carlo.rs @@ -29,7 +29,7 @@ use rand::distributions::{Distribution, Uniform}; fn main() { - let range = Uniform::new(-1.0f64, 1.0); + let range = Uniform::new(-1.0f64, 1.0).unwrap(); let mut rng = rand::thread_rng(); let total = 1_000_000; diff --git a/examples/monty-hall.rs b/examples/monty-hall.rs index 2a3b63d8df3..23e11178969 100644 --- a/examples/monty-hall.rs +++ b/examples/monty-hall.rs @@ -80,7 +80,7 @@ fn main() { let num_simulations = 10000; let mut rng = rand::thread_rng(); - let random_door = Uniform::new(0u32, 3); + let random_door = Uniform::new(0u32, 3).unwrap(); let (mut switch_wins, mut switch_losses) = (0, 0); let (mut keep_wins, mut keep_losses) = (0, 0); diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index f0e7114a657..7e703c01d2d 100644 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -49,7 +49,7 @@ static BATCH_SIZE: u64 = 10_000; static BATCHES: u64 = 1000; fn main() { - let range = Uniform::new(-1.0f64, 1.0); + let range = Uniform::new(-1.0f64, 1.0).unwrap(); let in_circle = (0..BATCHES) .into_par_iter() diff --git a/rand_chacha/src/lib.rs b/rand_chacha/src/lib.rs index 24125b45e10..f4b526b8f64 100644 --- a/rand_chacha/src/lib.rs +++ b/rand_chacha/src/lib.rs @@ -13,6 +13,7 @@ html_favicon_url = "https://www.rust-lang.org/favicon.ico", html_root_url = "https://rust-random.github.io/rand/" )] +#![forbid(unsafe_code)] #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 66389d670bf..13dac1c8a4d 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.5.0] - unreleased - Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) This breaks serialization compatibility with older versions. +- Upgrade Rand ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 6dbf7ab7494..0f49806aba1 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -163,8 +163,8 @@ impl Distribution for Binomial { // return value let mut y: i64; - let gen_u = Uniform::new(0., p4); - let gen_v = Uniform::new(0., 1.); + let gen_u = Uniform::new(0., p4).unwrap(); + let gen_v = Uniform::new(0., 1.).unwrap(); loop { // Step 1: Generate `u` for selecting the region. If region 1 is diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 4761450360d..a42a572d8d1 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -251,7 +251,7 @@ impl Distribution for Hypergeometric { x }, RejectionAcceptance { m, a, lambda_l, lambda_r, x_l, x_r, p1, p2, p3 } => { - let distr_region_select = Uniform::new(0.0, p3); + let distr_region_select = Uniform::new(0.0, p3).unwrap(); loop { let (y, v) = loop { let u = distr_region_select.sample(rng); diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 6d8d81bd2f3..c8fd298171d 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -11,6 +11,7 @@ html_favicon_url = "https://www.rust-lang.org/favicon.ico", html_root_url = "https://rust-random.github.io/rand/" )] +#![forbid(unsafe_code)] #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![allow( diff --git a/rand_distr/src/unit_ball.rs b/rand_distr/src/unit_ball.rs index 8a4b4fbf3d1..4d29612597f 100644 --- a/rand_distr/src/unit_ball.rs +++ b/rand_distr/src/unit_ball.rs @@ -31,7 +31,7 @@ pub struct UnitBall; impl Distribution<[F; 3]> for UnitBall { #[inline] fn sample(&self, rng: &mut R) -> [F; 3] { - let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()); + let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()).unwrap(); let mut x1; let mut x2; let mut x3; diff --git a/rand_distr/src/unit_circle.rs b/rand_distr/src/unit_circle.rs index 24a06f3f4de..f3dbe757aa9 100644 --- a/rand_distr/src/unit_circle.rs +++ b/rand_distr/src/unit_circle.rs @@ -35,7 +35,7 @@ pub struct UnitCircle; impl Distribution<[F; 2]> for UnitCircle { #[inline] fn sample(&self, rng: &mut R) -> [F; 2] { - let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()); + let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()).unwrap(); let mut x1; let mut x2; let mut sum; diff --git a/rand_distr/src/unit_disc.rs b/rand_distr/src/unit_disc.rs index 937c1d01b84..5004217d5b7 100644 --- a/rand_distr/src/unit_disc.rs +++ b/rand_distr/src/unit_disc.rs @@ -30,7 +30,7 @@ pub struct UnitDisc; impl Distribution<[F; 2]> for UnitDisc { #[inline] fn sample(&self, rng: &mut R) -> [F; 2] { - let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()); + let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()).unwrap(); let mut x1; let mut x2; loop { diff --git a/rand_distr/src/unit_sphere.rs b/rand_distr/src/unit_sphere.rs index 2b299239f49..632275e3327 100644 --- a/rand_distr/src/unit_sphere.rs +++ b/rand_distr/src/unit_sphere.rs @@ -34,7 +34,7 @@ pub struct UnitSphere; impl Distribution<[F; 3]> for UnitSphere { #[inline] fn sample(&self, rng: &mut R) -> [F; 3] { - let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()); + let uniform = Uniform::new(F::from(-1.).unwrap(), F::from(1.).unwrap()).unwrap(); loop { let (x1, x2) = (uniform.sample(rng), uniform.sample(rng)); let sum = x1 * x1 + x2 * x2; diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 582a4dd9ba8..170de80c4a5 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -221,8 +221,8 @@ impl WeightedAliasIndex { // Prepare distributions for sampling. Creating them beforehand improves // sampling performance. - let uniform_index = Uniform::new(0, n); - let uniform_within_weight_sum = Uniform::new(W::ZERO, weight_sum); + let uniform_index = Uniform::new(0, n).unwrap(); + let uniform_within_weight_sum = Uniform::new(W::ZERO, weight_sum).unwrap(); Ok(Self { aliases: aliases.aliases, @@ -458,7 +458,7 @@ mod test { let random_weight_distribution = Uniform::new_inclusive( W::ZERO, W::MAX / W::try_from_u32_lossy(NUM_WEIGHTS).unwrap(), - ); + ).unwrap(); for _ in 0..NUM_WEIGHTS { weights.push(rng.sample(&random_weight_distribution)); } diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index 9d0209d14fe..341313954e7 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -33,6 +33,7 @@ html_favicon_url = "https://www.rust-lang.org/favicon.ico", html_root_url = "https://rust-random.github.io/rand/" )] +#![forbid(unsafe_code)] #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![no_std] diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index 4fcb4c3246d..c6eaf5ef7ab 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -64,7 +64,7 @@ pub trait Distribution { /// .collect(); /// /// // Dice-rolling: - /// let die_range = Uniform::new_inclusive(1, 6); + /// let die_range = Uniform::new_inclusive(1, 6).unwrap(); /// let mut roll_die = die_range.sample_iter(&mut rng); /// while roll_die.next().unwrap() != 6 { /// println!("Not a 6; rolling again!"); @@ -93,7 +93,7 @@ pub trait Distribution { /// /// let mut rng = thread_rng(); /// - /// let die = Uniform::new_inclusive(1, 6); + /// let die = Uniform::new_inclusive(1, 6).unwrap(); /// let even_number = die.map(|num| num % 2 == 0); /// while !even_number.sample(&mut rng) { /// println!("Still odd; rolling again!"); @@ -227,7 +227,7 @@ mod tests { #[test] fn test_distributions_map() { - let dist = Uniform::new_inclusive(0, 5).map(|val| val + 15); + let dist = Uniform::new_inclusive(0, 5).unwrap().map(|val| val + 15); let mut rng = crate::test::rng(212); let val = dist.sample(&mut rng); @@ -240,6 +240,7 @@ mod tests { rng: &mut R, ) -> impl Iterator + '_ { Uniform::new_inclusive(1, 6) + .unwrap() .sample_iter(rng) .filter(|x| *x != 5) .take(10) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 4cb31086734..9ddce76366e 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -81,9 +81,9 @@ impl Distribution for Standard { // reserved for surrogates. This is the size of that gap. const GAP_SIZE: u32 = 0xDFFF - 0xD800 + 1; - // Uniform::new(0, 0x11_0000 - GAP_SIZE) can also be used but it + // Uniform::new(0, 0x11_0000 - GAP_SIZE) can also be used, but it // seemed slower. - let range = Uniform::new(GAP_SIZE, 0x11_0000); + let range = Uniform::new(GAP_SIZE, 0x11_0000).unwrap(); let mut n = range.sample(rng); if n <= 0xDFFF { diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 3302deb2a40..398cad18b2c 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -75,7 +75,7 @@ impl<'a, T> Slice<'a, T> { 0 => Err(EmptySlice), len => Ok(Self { slice, - range: Uniform::new(0, len), + range: Uniform::new(0, len).unwrap(), }), } } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 49703b84e46..b4856ff6131 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -10,9 +10,8 @@ //! A distribution uniformly sampling numbers within a given range. //! //! [`Uniform`] is the standard distribution to sample uniformly from a range; -//! e.g. `Uniform::new_inclusive(1, 6)` can sample integers from 1 to 6, like a -//! standard die. [`Rng::gen_range`] supports any type supported by -//! [`Uniform`]. +//! e.g. `Uniform::new_inclusive(1, 6).unwrap()` can sample integers from 1 to 6, like a +//! standard die. [`Rng::gen_range`] supports any type supported by [`Uniform`]. //! //! This distribution is provided with support for several primitive types //! (all integer and floating-point types) as well as [`std::time::Duration`], @@ -31,7 +30,7 @@ //! use rand::distributions::Uniform; //! //! let mut rng = thread_rng(); -//! let side = Uniform::new(-10.0, 10.0); +//! let side = Uniform::new(-10.0, 10.0).unwrap(); //! //! // sample between 1 and 10 points //! for _ in 0..rng.gen_range(1..=10) { @@ -49,11 +48,11 @@ //! //! At a minimum, the back-end needs to store any parameters needed for sampling //! (e.g. the target range) and implement `new`, `new_inclusive` and `sample`. -//! Those methods should include an assert to check the range is valid (i.e. +//! Those methods should include an assertion to check the range is valid (i.e. //! `low < high`). The example below merely wraps another back-end. //! //! The `new`, `new_inclusive` and `sample_single` functions use arguments of -//! type SampleBorrow in order to support passing in values by reference or +//! type SampleBorrow to support passing in values by reference or //! by value. In the implementation of these functions, you can choose to //! simply use the reference returned by [`SampleBorrow::borrow`], or you can choose //! to copy or clone the value, whatever is appropriate for your type. @@ -61,7 +60,7 @@ //! ``` //! use rand::prelude::*; //! use rand::distributions::uniform::{Uniform, SampleUniform, -//! UniformSampler, UniformFloat, SampleBorrow}; +//! UniformSampler, UniformFloat, SampleBorrow, Error}; //! //! struct MyF32(f32); //! @@ -70,20 +69,18 @@ //! //! impl UniformSampler for UniformMyF32 { //! type X = MyF32; -//! fn new(low: B1, high: B2) -> Self +//! +//! fn new(low: B1, high: B2) -> Result //! where B1: SampleBorrow + Sized, //! B2: SampleBorrow + Sized //! { -//! UniformMyF32(UniformFloat::::new(low.borrow().0, high.borrow().0)) +//! UniformFloat::::new(low.borrow().0, high.borrow().0).map(UniformMyF32) //! } -//! fn new_inclusive(low: B1, high: B2) -> Self +//! fn new_inclusive(low: B1, high: B2) -> Result //! where B1: SampleBorrow + Sized, //! B2: SampleBorrow + Sized //! { -//! UniformMyF32(UniformFloat::::new_inclusive( -//! low.borrow().0, -//! high.borrow().0, -//! )) +//! UniformFloat::::new_inclusive(low.borrow().0, high.borrow().0).map(UniformMyF32) //! } //! fn sample(&self, rng: &mut R) -> Self::X { //! MyF32(self.0.sample(rng)) @@ -95,7 +92,7 @@ //! } //! //! let (low, high) = (MyF32(17.0f32), MyF32(22.0f32)); -//! let uniform = Uniform::new(low, high); +//! let uniform = Uniform::new(low, high).unwrap(); //! let x = uniform.sample(&mut thread_rng()); //! ``` //! @@ -106,8 +103,10 @@ //! [`UniformDuration`]: crate::distributions::uniform::UniformDuration //! [`SampleBorrow::borrow`]: crate::distributions::uniform::SampleBorrow::borrow +use core::fmt; use core::time::Duration; use core::ops::{Range, RangeInclusive}; +use core::convert::TryFrom; use crate::distributions::float::IntoFloat; use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD, WideningMultiply}; @@ -122,6 +121,28 @@ use crate::distributions::utils::Float; #[cfg(feature = "simd_support")] use core::simd::*; +/// Error type returned from [`Uniform::new`] and `new_inclusive`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// `low > high`, or equal in case of exclusive range. + EmptyRange, + /// Input or range `high - low` is non-finite. Not relevant to integer types. + NonFinite, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::EmptyRange => "low > high (or equal if exclusive) in uniform distribution", + Error::NonFinite => "Non-finite range in uniform distribution", + }) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] +impl std::error::Error for Error {} + #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; @@ -134,10 +155,10 @@ use serde::{Serialize, Deserialize}; /// /// When sampling from a constant range, many calculations can happen at /// compile-time and all methods should be fast; for floating-point ranges and -/// the full range of integer types this should have comparable performance to +/// the full range of integer types, this should have comparable performance to /// the `Standard` distribution. /// -/// Steps are taken to avoid bias which might be present in naive +/// Steps are taken to avoid bias, which might be present in naive /// implementations; for example `rng.gen::() % 170` samples from the range /// `[0, 169]` but is twice as likely to select numbers less than 85 than other /// values. Further, the implementations here give more weight to the high-bits @@ -153,7 +174,7 @@ use serde::{Serialize, Deserialize}; /// ``` /// use rand::distributions::{Distribution, Uniform}; /// -/// let between = Uniform::from(10..10000); +/// let between = Uniform::try_from(10..10000).unwrap(); /// let mut rng = rand::thread_rng(); /// let mut sum = 0; /// for _ in 0..1000 { @@ -181,24 +202,30 @@ use serde::{Serialize, Deserialize}; pub struct Uniform(X::Sampler); impl Uniform { - /// Create a new `Uniform` instance which samples uniformly from the half - /// open range `[low, high)` (excluding `high`). Panics if `low >= high`. - pub fn new(low: B1, high: B2) -> Uniform + /// Create a new `Uniform` instance, which samples uniformly from the half + /// open range `[low, high)` (excluding `high`). + /// + /// Fails if `low >= high`, or if `low`, `high` or the range `high - low` is + /// non-finite. In release mode, only the range is checked. + pub fn new(low: B1, high: B2) -> Result, Error> where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - Uniform(X::Sampler::new(low, high)) + X::Sampler::new(low, high).map(Uniform) } - /// Create a new `Uniform` instance which samples uniformly from the closed - /// range `[low, high]` (inclusive). Panics if `low > high`. - pub fn new_inclusive(low: B1, high: B2) -> Uniform + /// Create a new `Uniform` instance, which samples uniformly from the closed + /// range `[low, high]` (inclusive). + /// + /// Fails if `low > high`, or if `low`, `high` or the range `high - low` is + /// non-finite. In release mode, only the range is checked. + pub fn new_inclusive(low: B1, high: B2) -> Result, Error> where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - Uniform(X::Sampler::new_inclusive(low, high)) + X::Sampler::new_inclusive(low, high).map(Uniform) } } @@ -234,22 +261,20 @@ pub trait UniformSampler: Sized { /// The type sampled by this implementation. type X; - /// Construct self, with inclusive lower bound and exclusive upper bound - /// `[low, high)`. + /// Construct self, with inclusive lower bound and exclusive upper bound `[low, high)`. /// - /// Usually users should not call this directly but instead use - /// `Uniform::new`, which asserts that `low < high` before calling this. - fn new(low: B1, high: B2) -> Self + /// Usually users should not call this directly but prefer to use + /// [`Uniform::new`]. + fn new(low: B1, high: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized; /// Construct self, with inclusive bounds `[low, high]`. /// - /// Usually users should not call this directly but instead use - /// `Uniform::new_inclusive`, which asserts that `low <= high` before - /// calling this. - fn new_inclusive(low: B1, high: B2) -> Self + /// Usually users should not call this directly but prefer to use + /// [`Uniform::new_inclusive`]. + fn new_inclusive(low: B1, high: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized; @@ -273,16 +298,16 @@ pub trait UniformSampler: Sized { /// # #[allow(unused)] /// fn sample_from_range(lb: T, ub: T) -> T { /// let mut rng = thread_rng(); - /// ::Sampler::sample_single(lb, ub, &mut rng) + /// ::Sampler::sample_single(lb, ub, &mut rng).unwrap() /// } /// ``` - fn sample_single(low: B1, high: B2, rng: &mut R) -> Self::X + fn sample_single(low: B1, high: B2, rng: &mut R) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - let uniform: Self = UniformSampler::new(low, high); - uniform.sample(rng) + let uniform: Self = UniformSampler::new(low, high)?; + Ok(uniform.sample(rng)) } /// Sample a single value uniformly from a range with inclusive lower bound @@ -294,23 +319,27 @@ pub trait UniformSampler: Sized { /// via this method. /// Results may not be identical. fn sample_single_inclusive(low: B1, high: B2, rng: &mut R) - -> Self::X + -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { - let uniform: Self = UniformSampler::new_inclusive(low, high); - uniform.sample(rng) + let uniform: Self = UniformSampler::new_inclusive(low, high)?; + Ok(uniform.sample(rng)) } } -impl From> for Uniform { - fn from(r: ::core::ops::Range) -> Uniform { +impl TryFrom> for Uniform { + type Error = Error; + + fn try_from(r: ::core::ops::Range) -> Result, Error> { Uniform::new(r.start, r.end) } } -impl From> for Uniform { - fn from(r: ::core::ops::RangeInclusive) -> Uniform { +impl TryFrom> for Uniform { + type Error = Error; + + fn try_from(r: ::core::ops::RangeInclusive) -> Result, Error> { Uniform::new_inclusive(r.start(), r.end()) } } @@ -350,7 +379,7 @@ where Borrowed: SampleUniform /// for `Rng::gen_range`. pub trait SampleRange { /// Generate a sample from the given range. - fn sample_single(self, rng: &mut R) -> T; + fn sample_single(self, rng: &mut R) -> Result; /// Check whether the range is empty. fn is_empty(&self) -> bool; @@ -358,7 +387,7 @@ pub trait SampleRange { impl SampleRange for Range { #[inline] - fn sample_single(self, rng: &mut R) -> T { + fn sample_single(self, rng: &mut R) -> Result { T::Sampler::sample_single(self.start, self.end, rng) } @@ -370,7 +399,7 @@ impl SampleRange for Range { impl SampleRange for RangeInclusive { #[inline] - fn sample_single(self, rng: &mut R) -> T { + fn sample_single(self, rng: &mut R) -> Result { T::Sampler::sample_single_inclusive(self.start(), self.end(), rng) } @@ -444,30 +473,31 @@ macro_rules! uniform_int_impl { #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low < high, "Uniform::new called with `low >= high`"); + if !(low < high) { + return Err(Error::EmptyRange); + } UniformSampler::new_inclusive(low, high - 1) } #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!( - low <= high, - "Uniform::new_inclusive called with `low > high`" - ); + if !(low <= high) { + return Err(Error::EmptyRange); + } let unsigned_max = ::core::$u_large::MAX; let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned; @@ -478,12 +508,12 @@ macro_rules! uniform_int_impl { 0 }; - UniformInt { + Ok(UniformInt { low, // These are really $unsigned values, but store as $ty: range: range as $ty, z: ints_to_reject as $unsigned as $ty, - } + }) } #[inline] @@ -506,31 +536,35 @@ macro_rules! uniform_int_impl { } #[inline] - fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Self::X + fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low < high, "UniformSampler::sample_single: low >= high"); + if !(low < high) { + return Err(Error::EmptyRange); + } Self::sample_single_inclusive(low, high - 1, rng) } #[inline] - fn sample_single_inclusive(low_b: B1, high_b: B2, rng: &mut R) -> Self::X + fn sample_single_inclusive(low_b: B1, high_b: B2, rng: &mut R) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low <= high, "UniformSampler::sample_single_inclusive: low > high"); + if !(low <= high) { + return Err(Error::EmptyRange); + } let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned as $u_large; // If the above resulted in wrap-around to 0, the range is $ty::MIN..=$ty::MAX, // and any integer will do. if range == 0 { - return rng.gen(); + return Ok(rng.gen()); } let zone = if ::core::$unsigned::MAX <= ::core::u16::MAX as $unsigned { @@ -550,7 +584,7 @@ macro_rules! uniform_int_impl { let v: $u_large = rng.gen(); let (hi, lo) = v.wmul(range); if lo <= zone { - return low.wrapping_add(hi as $ty); + return Ok(low.wrapping_add(hi as $ty)); } } } @@ -600,26 +634,29 @@ macro_rules! uniform_simd_int_impl { #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.simd_lt(high).all(), "Uniform::new called with `low >= high`"); + if !(low.simd_lt(high).all()) { + return Err(Error::EmptyRange); + } UniformSampler::new_inclusive(low, high - Simd::splat(1)) } #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low.simd_le(high).all(), - "Uniform::new_inclusive called with `low > high`"); + if !(low.simd_le(high).all()) { + return Err(Error::EmptyRange); + } let unsigned_max = Simd::splat(::core::$unsigned::MAX); // NOTE: all `Simd` operations are inherently wrapping, @@ -636,12 +673,12 @@ macro_rules! uniform_simd_int_impl { // zero which means only one sample is needed. let zone = unsigned_max - ints_to_reject; - UniformInt { + Ok(UniformInt { low, // These are really $unsigned values, but store as $ty: range: range.cast(), z: zone.cast(), - } + }) } fn sample(&self, rng: &mut R) -> Self::X { @@ -727,7 +764,7 @@ impl UniformSampler for UniformChar { #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -735,12 +772,12 @@ impl UniformSampler for UniformChar { let low = char_to_comp_u32(*low_b.borrow()); let high = char_to_comp_u32(*high_b.borrow()); let sampler = UniformInt::::new(low, high); - UniformChar { sampler } + sampler.map(|sampler| UniformChar { sampler }) } #[inline] // if the range is constant, this helps LLVM to do the // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -748,7 +785,7 @@ impl UniformSampler for UniformChar { let low = char_to_comp_u32(*low_b.borrow()); let high = char_to_comp_u32(*high_b.borrow()); let sampler = UniformInt::::new_inclusive(low, high); - UniformChar { sampler } + sampler.map(|sampler| UniformChar { sampler }) } fn sample(&self, rng: &mut R) -> Self::X { @@ -798,28 +835,28 @@ macro_rules! uniform_float_impl { impl UniformSampler for UniformFloat<$ty> { type X = $ty; - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - debug_assert!( - low.all_finite(), - "Uniform::new called with `low` non-finite." - ); - debug_assert!( - high.all_finite(), - "Uniform::new called with `high` non-finite." - ); - assert!(low.all_lt(high), "Uniform::new called with `low >= high`"); + #[cfg(debug_assertions)] + if !(low.all_finite()) || !(high.all_finite()) { + return Err(Error::NonFinite); + } + if !(low.all_lt(high)) { + return Err(Error::EmptyRange); + } let max_rand = <$ty>::splat( (::core::$u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, ); let mut scale = high - low; - assert!(scale.all_finite(), "Uniform::new: range overflow"); + if !(scale.all_finite()) { + return Err(Error::NonFinite); + } loop { let mask = (scale * max_rand + low).ge_mask(high); @@ -831,34 +868,31 @@ macro_rules! uniform_float_impl { debug_assert!(<$ty>::splat(0.0).all_le(scale)); - UniformFloat { low, scale } + Ok(UniformFloat { low, scale }) } - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - debug_assert!( - low.all_finite(), - "Uniform::new_inclusive called with `low` non-finite." - ); - debug_assert!( - high.all_finite(), - "Uniform::new_inclusive called with `high` non-finite." - ); - assert!( - low.all_le(high), - "Uniform::new_inclusive called with `low > high`" - ); + #[cfg(debug_assertions)] + if !(low.all_finite()) || !(high.all_finite()) { + return Err(Error::NonFinite); + } + if !low.all_le(high) { + return Err(Error::EmptyRange); + } let max_rand = <$ty>::splat( (::core::$u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, ); let mut scale = (high - low) / max_rand; - assert!(scale.all_finite(), "Uniform::new_inclusive: range overflow"); + if !scale.all_finite() { + return Err(Error::NonFinite); + } loop { let mask = (scale * max_rand + low).gt_mask(high); @@ -870,15 +904,14 @@ macro_rules! uniform_float_impl { debug_assert!(<$ty>::splat(0.0).all_le(scale)); - UniformFloat { low, scale } + Ok(UniformFloat { low, scale }) } fn sample(&self, rng: &mut R) -> Self::X { // Generate a value in the range [1, 2) let value1_2 = (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); - // Get a value in the range [0, 1) in order to avoid - // overflowing into infinity when multiplying with scale + // Get a value in the range [0, 1) to avoid overflow when multiplying by scale let value0_1 = value1_2 - <$ty>::splat(1.0); // We don't use `f64::mul_add`, because it is not available with @@ -890,35 +923,31 @@ macro_rules! uniform_float_impl { } #[inline] - fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Self::X + fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - debug_assert!( - low.all_finite(), - "UniformSampler::sample_single called with `low` non-finite." - ); - debug_assert!( - high.all_finite(), - "UniformSampler::sample_single called with `high` non-finite." - ); - assert!( - low.all_lt(high), - "UniformSampler::sample_single: low >= high" - ); + #[cfg(debug_assertions)] + if !low.all_finite() || !high.all_finite() { + return Err(Error::NonFinite); + } + if !low.all_lt(high) { + return Err(Error::EmptyRange); + } let mut scale = high - low; - assert!(scale.all_finite(), "UniformSampler::sample_single: range overflow"); + if !scale.all_finite() { + return Err(Error::NonFinite); + } loop { // Generate a value in the range [1, 2) let value1_2 = (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); - // Get a value in the range [0, 1) in order to avoid - // overflowing into infinity when multiplying with scale + // Get a value in the range [0, 1) to avoid overflow when multiplying by scale let value0_1 = value1_2 - <$ty>::splat(1.0); // Doing multiply before addition allows some architectures @@ -927,7 +956,7 @@ macro_rules! uniform_float_impl { debug_assert!(low.all_le(res) || !scale.all_finite()); if res.all_lt(high) { - return res; + return Ok(res); } // This handles a number of edge cases. @@ -955,14 +984,13 @@ macro_rules! uniform_float_impl { // `high` are non-finite we'll end up here and can do the // appropriate checks. // - // Likewise `high - low` overflowing to infinity is also + // Likewise, `high - low` overflowing to infinity is also // rare, so handle it here after the common case. let mask = !scale.finite_mask(); if mask.any() { - assert!( - low.all_finite() && high.all_finite(), - "Uniform::sample_single: low and high must be finite" - ); + if !(low.all_finite() && high.all_finite()) { + return Err(Error::NonFinite); + } scale = scale.decrease_masked(mask); } } @@ -1027,29 +1055,30 @@ impl UniformSampler for UniformDuration { type X = Duration; #[inline] - fn new(low_b: B1, high_b: B2) -> Self + fn new(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!(low < high, "Uniform::new called with `low >= high`"); + if !(low < high) { + return Err(Error::EmptyRange); + } UniformDuration::new_inclusive(low, high - Duration::new(0, 1)) } #[inline] - fn new_inclusive(low_b: B1, high_b: B2) -> Self + fn new_inclusive(low_b: B1, high_b: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { let low = *low_b.borrow(); let high = *high_b.borrow(); - assert!( - low <= high, - "Uniform::new_inclusive called with `low > high`" - ); + if !(low <= high) { + return Err(Error::EmptyRange); + } let low_s = low.as_secs(); let low_n = low.subsec_nanos(); @@ -1064,7 +1093,7 @@ impl UniformSampler for UniformDuration { let mode = if low_s == high_s { UniformDurationMode::Small { secs: low_s, - nanos: Uniform::new_inclusive(low_n, high_n), + nanos: Uniform::new_inclusive(low_n, high_n)?, } } else { let max = high_s @@ -1074,7 +1103,7 @@ impl UniformSampler for UniformDuration { if let Some(higher_bound) = max { let lower_bound = low_s * 1_000_000_000 + u64::from(low_n); UniformDurationMode::Medium { - nanos: Uniform::new_inclusive(lower_bound, higher_bound), + nanos: Uniform::new_inclusive(lower_bound, higher_bound)?, } } else { // An offset is applied to simplify generation of nanoseconds @@ -1082,14 +1111,14 @@ impl UniformSampler for UniformDuration { UniformDurationMode::Large { max_secs: high_s, max_nanos, - secs: Uniform::new_inclusive(low_s, high_s), + secs: Uniform::new_inclusive(low_s, high_s)?, } } }; - UniformDuration { + Ok(UniformDuration { mode, offset: low_n, - } + }) } #[inline] @@ -1109,7 +1138,7 @@ impl UniformSampler for UniformDuration { secs, } => { // constant folding means this is at least as fast as `Rng::sample(Range)` - let nano_range = Uniform::new(0, 1_000_000_000); + let nano_range = Uniform::new(0, 1_000_000_000).unwrap(); loop { let s = secs.sample(rng); let n = nano_range.sample(rng); @@ -1131,7 +1160,7 @@ mod tests { #[test] #[cfg(feature = "serde1")] fn test_serialization_uniform_duration() { - let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)); + let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)).unwrap(); let de_distr: UniformDuration = bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); assert_eq!( distr.offset, de_distr.offset @@ -1164,39 +1193,37 @@ mod tests { #[test] #[cfg(feature = "serde1")] fn test_uniform_serialization() { - let unit_box: Uniform = Uniform::new(-1, 1); + let unit_box: Uniform = Uniform::new(-1, 1).unwrap(); let de_unit_box: Uniform = bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); assert_eq!(unit_box.0.low, de_unit_box.0.low); assert_eq!(unit_box.0.range, de_unit_box.0.range); assert_eq!(unit_box.0.z, de_unit_box.0.z); - let unit_box: Uniform = Uniform::new(-1., 1.); + let unit_box: Uniform = Uniform::new(-1., 1.).unwrap(); let de_unit_box: Uniform = bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); assert_eq!(unit_box.0.low, de_unit_box.0.low); assert_eq!(unit_box.0.scale, de_unit_box.0.scale); } - #[should_panic] #[test] fn test_uniform_bad_limits_equal_int() { - Uniform::new(10, 10); + assert_eq!(Uniform::new(10, 10), Err(Error::EmptyRange)); } #[test] fn test_uniform_good_limits_equal_int() { let mut rng = crate::test::rng(804); - let dist = Uniform::new_inclusive(10, 10); + let dist = Uniform::new_inclusive(10, 10).unwrap(); for _ in 0..20 { assert_eq!(rng.sample(dist), 10); } } - #[should_panic] #[test] fn test_uniform_bad_limits_flipped_int() { - Uniform::new(10, 5); + assert_eq!(Uniform::new(10, 5), Err(Error::EmptyRange)); } #[test] @@ -1210,37 +1237,37 @@ mod tests { macro_rules! t { ($ty:ident, $v:expr, $le:expr, $lt:expr) => {{ for &(low, high) in $v.iter() { - let my_uniform = Uniform::new(low, high); + let my_uniform = Uniform::new(low, high).unwrap(); for _ in 0..1000 { let v: $ty = rng.sample(my_uniform); assert!($le(low, v) && $lt(v, high)); } - let my_uniform = Uniform::new_inclusive(low, high); + let my_uniform = Uniform::new_inclusive(low, high).unwrap(); for _ in 0..1000 { let v: $ty = rng.sample(my_uniform); assert!($le(low, v) && $le(v, high)); } - let my_uniform = Uniform::new(&low, high); + let my_uniform = Uniform::new(&low, high).unwrap(); for _ in 0..1000 { let v: $ty = rng.sample(my_uniform); assert!($le(low, v) && $lt(v, high)); } - let my_uniform = Uniform::new_inclusive(&low, &high); + let my_uniform = Uniform::new_inclusive(&low, &high).unwrap(); for _ in 0..1000 { let v: $ty = rng.sample(my_uniform); assert!($le(low, v) && $le(v, high)); } for _ in 0..1000 { - let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng); + let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng).unwrap(); assert!($le(low, v) && $lt(v, high)); } for _ in 0..1000 { - let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng); + let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng).unwrap(); assert!($le(low, v) && $le(v, high)); } } @@ -1299,7 +1326,7 @@ mod tests { let d = Uniform::new( core::char::from_u32(0xD7F0).unwrap(), core::char::from_u32(0xE010).unwrap(), - ); + ).unwrap(); for _ in 0..100 { let c = d.sample(&mut rng); assert!((c as u32) < 0xD800 || (c as u32) > 0xDFFF); @@ -1330,27 +1357,27 @@ mod tests { for lane in 0..<$ty>::LANES { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); - let my_uniform = Uniform::new(low, high); - let my_incl_uniform = Uniform::new_inclusive(low, high); + let my_uniform = Uniform::new(low, high).unwrap(); + let my_incl_uniform = Uniform::new_inclusive(low, high).unwrap(); for _ in 0..100 { let v = rng.sample(my_uniform).extract(lane); assert!(low_scalar <= v && v < high_scalar); let v = rng.sample(my_incl_uniform).extract(lane); assert!(low_scalar <= v && v <= high_scalar); let v = <$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut rng).extract(lane); + ::sample_single(low, high, &mut rng).unwrap().extract(lane); assert!(low_scalar <= v && v < high_scalar); } assert_eq!( - rng.sample(Uniform::new_inclusive(low, low)).extract(lane), + rng.sample(Uniform::new_inclusive(low, low).unwrap()).extract(lane), low_scalar ); assert_eq!(zero_rng.sample(my_uniform).extract(lane), low_scalar); assert_eq!(zero_rng.sample(my_incl_uniform).extract(lane), low_scalar); assert_eq!(<$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut zero_rng) + ::sample_single(low, high, &mut zero_rng).unwrap() .extract(lane), low_scalar); assert!(max_rng.sample(my_uniform).extract(lane) < high_scalar); assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar); @@ -1365,7 +1392,7 @@ mod tests { ); assert!( <$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut lowering_max_rng) + ::sample_single(low, high, &mut lowering_max_rng).unwrap() .extract(lane) < high_scalar ); } @@ -1376,14 +1403,14 @@ mod tests { rng.sample(Uniform::new_inclusive( ::core::$f_scalar::MAX, ::core::$f_scalar::MAX - )), + ).unwrap()), ::core::$f_scalar::MAX ); assert_eq!( rng.sample(Uniform::new_inclusive( -::core::$f_scalar::MAX, -::core::$f_scalar::MAX - )), + ).unwrap()), -::core::$f_scalar::MAX ); }}; @@ -1404,9 +1431,8 @@ mod tests { } #[test] - #[should_panic] fn test_float_overflow() { - let _ = Uniform::from(::core::f64::MIN..::core::f64::MAX); + assert_eq!(Uniform::try_from(::core::f64::MIN..::core::f64::MAX), Err(Error::NonFinite)); } #[test] @@ -1427,7 +1453,7 @@ mod tests { use std::panic::catch_unwind; fn range(low: T, high: T) { let mut rng = crate::test::rng(253); - T::Sampler::sample_single(low, high, &mut rng); + T::Sampler::sample_single(low, high, &mut rng).unwrap(); } macro_rules! t { @@ -1454,10 +1480,10 @@ mod tests { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); assert!(catch_unwind(|| range(low, high)).is_err()); - assert!(catch_unwind(|| Uniform::new(low, high)).is_err()); - assert!(catch_unwind(|| Uniform::new_inclusive(low, high)).is_err()); + assert!(Uniform::new(low, high).is_err()); + assert!(Uniform::new_inclusive(low, high).is_err()); assert!(catch_unwind(|| range(low, low)).is_err()); - assert!(catch_unwind(|| Uniform::new(low, low)).is_err()); + assert!(Uniform::new(low, low).is_err()); } } }}; @@ -1492,7 +1518,7 @@ mod tests { ), ]; for &(low, high) in v.iter() { - let my_uniform = Uniform::new(low, high); + let my_uniform = Uniform::new(low, high).unwrap(); for _ in 0..1000 { let v = rng.sample(my_uniform); assert!(low <= v && v < high); @@ -1514,15 +1540,15 @@ mod tests { impl UniformSampler for UniformMyF32 { type X = MyF32; - fn new(low: B1, high: B2) -> Self + fn new(low: B1, high: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - UniformMyF32(UniformFloat::::new(low.borrow().x, high.borrow().x)) + UniformFloat::::new(low.borrow().x, high.borrow().x).map(UniformMyF32) } - fn new_inclusive(low: B1, high: B2) -> Self + fn new_inclusive(low: B1, high: B2) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -1541,7 +1567,7 @@ mod tests { } let (low, high) = (MyF32 { x: 17.0f32 }, MyF32 { x: 22.0f32 }); - let uniform = Uniform::new(low, high); + let uniform = Uniform::new(low, high).unwrap(); let mut rng = crate::test::rng(804); for _ in 0..100 { let x: MyF32 = rng.sample(uniform); @@ -1551,20 +1577,20 @@ mod tests { #[test] fn test_uniform_from_std_range() { - let r = Uniform::from(2u32..7); + let r = Uniform::try_from(2u32..7).unwrap(); assert_eq!(r.0.low, 2); assert_eq!(r.0.range, 5); - let r = Uniform::from(2.0f64..7.0); + let r = Uniform::try_from(2.0f64..7.0).unwrap(); assert_eq!(r.0.low, 2.0); assert_eq!(r.0.scale, 5.0); } #[test] fn test_uniform_from_std_range_inclusive() { - let r = Uniform::from(2u32..=6); + let r = Uniform::try_from(2u32..=6).unwrap(); assert_eq!(r.0.low, 2); assert_eq!(r.0.range, 5); - let r = Uniform::from(2.0f64..=7.0); + let r = Uniform::try_from(2.0f64..=7.0).unwrap(); assert_eq!(r.0.low, 2.0); assert!(r.0.scale > 5.0); assert!(r.0.scale < 5.0 + 1e-14); @@ -1579,11 +1605,11 @@ mod tests { let mut buf = [lb; 3]; for x in &mut buf { - *x = T::Sampler::sample_single(lb, ub, &mut rng); + *x = T::Sampler::sample_single(lb, ub, &mut rng).unwrap(); } assert_eq!(&buf, expected_single); - let distr = Uniform::new(lb, ub); + let distr = Uniform::new(lb, ub).unwrap(); for x in &mut buf { *x = rng.sample(&distr); } @@ -1626,9 +1652,9 @@ mod tests { #[test] fn uniform_distributions_can_be_compared() { - assert_eq!(Uniform::new(1.0, 2.0), Uniform::new(1.0, 2.0)); + assert_eq!(Uniform::new(1.0, 2.0).unwrap(), Uniform::new(1.0, 2.0).unwrap()); // To cover UniformInt - assert_eq!(Uniform::new(1 as u32, 2 as u32), Uniform::new(1 as u32, 2 as u32)); + assert_eq!(Uniform::new(1 as u32, 2 as u32).unwrap(), Uniform::new(1 as u32, 2 as u32).unwrap()); } } diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 0131ed157c0..4c57edc5f60 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -123,7 +123,7 @@ impl WeightedIndex { if total_weight == zero { return Err(WeightedError::AllWeightsZero); } - let distr = X::Sampler::new(zero, total_weight.clone()); + let distr = X::Sampler::new(zero, total_weight.clone()).unwrap(); Ok(WeightedIndex { cumulative_weights: weights, @@ -220,7 +220,7 @@ impl WeightedIndex { } self.total_weight = total_weight; - self.weight_distribution = X::Sampler::new(zero, self.total_weight.clone()); + self.weight_distribution = X::Sampler::new(zero, self.total_weight.clone()).unwrap(); Ok(()) } @@ -230,7 +230,6 @@ impl Distribution for WeightedIndex where X: SampleUniform + PartialOrd { fn sample(&self, rng: &mut R) -> usize { - use ::core::cmp::Ordering; let chosen_weight = self.weight_distribution.sample(rng); // Find the first item which has a weight *higher* than the chosen weight. self.cumulative_weights.partition_point(|w| w <= &chosen_weight) diff --git a/src/rng.rs b/src/rng.rs index c9f3a5f72e5..1b53298d511 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -130,7 +130,7 @@ pub trait Rng: RngCore { R: SampleRange { assert!(!range.is_empty(), "cannot sample empty range"); - range.sample_single(self) + range.sample_single(self).unwrap() } /// Sample a new value, using the given distribution. @@ -142,10 +142,10 @@ pub trait Rng: RngCore { /// use rand::distributions::Uniform; /// /// let mut rng = thread_rng(); - /// let x = rng.sample(Uniform::new(10u32, 15)); + /// let x = rng.sample(Uniform::new(10u32, 15).unwrap()); /// // Type annotation requires two types, the type and distribution; the /// // distribution can be inferred. - /// let y = rng.sample::(Uniform::new(10, 15)); + /// let y = rng.sample::(Uniform::new(10, 15).unwrap()); /// ``` fn sample>(&mut self, distr: D) -> T { distr.sample(self) @@ -181,7 +181,7 @@ pub trait Rng: RngCore { /// .collect::>()); /// /// // Dice-rolling: - /// let die_range = Uniform::new_inclusive(1, 6); + /// let die_range = Uniform::new_inclusive(1, 6).unwrap(); /// let mut roll_die = (&mut rng).sample_iter(die_range); /// while roll_die.next().unwrap() != 6 { /// println!("Not a 6; rolling again!"); diff --git a/src/seq/index.rs b/src/seq/index.rs index b7bc6a9b26a..50523cc47c4 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -479,7 +479,7 @@ where let mut cache = HashSet::with_capacity(amount.as_usize()); #[cfg(not(feature = "std"))] let mut cache = BTreeSet::new(); - let distr = Uniform::new(X::zero(), length); + let distr = Uniform::new(X::zero(), length).unwrap(); let mut indices = Vec::with_capacity(amount.as_usize()); for _ in 0..amount.as_usize() { let mut pos = distr.sample(rng); From f2e21db9713413d390106d8d9a718ec0fc9d20a9 Mon Sep 17 00:00:00 2001 From: Oliver Tearne Date: Mon, 13 Feb 2023 09:08:01 +0000 Subject: [PATCH 283/443] Poisson returns -1 for small lambda (#1284) * Correct Knuth's method since not using do-while --- rand_distr/CHANGELOG.md | 1 + rand_distr/src/poisson.rs | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 13dac1c8a4d..50cd913b21f 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) This breaks serialization compatibility with older versions. - Upgrade Rand +- Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 8b9bffd020e..b0b3d15a8ae 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -89,8 +89,8 @@ where F: Float + FloatConst, Standard: Distribution // for low expected values use the Knuth method if self.lambda < F::from(12.0).unwrap() { - let mut result = F::zero(); - let mut p = F::one(); + let mut result = F::one(); + let mut p = rng.gen::(); while p > self.exp_lambda { p = p*rng.gen::(); result = result + F::one(); @@ -161,10 +161,15 @@ mod test { #[test] fn test_poisson_avg() { - test_poisson_avg_gen::(10.0, 0.5); - test_poisson_avg_gen::(15.0, 0.5); - test_poisson_avg_gen::(10.0, 0.5); - test_poisson_avg_gen::(15.0, 0.5); + test_poisson_avg_gen::(10.0, 0.1); + test_poisson_avg_gen::(15.0, 0.1); + + test_poisson_avg_gen::(10.0, 0.1); + test_poisson_avg_gen::(15.0, 0.1); + + //Small lambda will use Knuth's method with exp_lambda == 1.0 + test_poisson_avg_gen::(0.00000000000000005, 0.1); + test_poisson_avg_gen::(0.00000000000000005, 0.1); } #[test] From 7c1e649ea1b8074046712e07272d18a3d85bcc71 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Mon, 20 Feb 2023 10:28:23 +0000 Subject: [PATCH 284/443] Rework CryptoRng (#1273) --- rand_chacha/src/chacha.rs | 16 ++++++++-------- rand_core/src/block.rs | 14 +++++++++++--- rand_core/src/lib.rs | 35 +++-------------------------------- src/rngs/adapter/reseeding.rs | 12 ++++++------ 4 files changed, 28 insertions(+), 49 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index ad74b35f62b..ebc28a8ab04 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -13,7 +13,7 @@ use self::core::fmt; use crate::guts::ChaCha; -use rand_core::block::{BlockRng, BlockRngCore}; +use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize, Serializer, Deserializer}; @@ -99,7 +99,7 @@ macro_rules! chacha_impl { } } - impl CryptoRng for $ChaChaXCore {} + impl CryptoBlockRng for $ChaChaXCore {} /// A cryptographically secure random number generator that uses the ChaCha algorithm. /// @@ -626,12 +626,12 @@ mod test { #[test] fn test_trait_objects() { - use rand_core::CryptoRngCore; + use rand_core::CryptoRng; - let rng = &mut ChaChaRng::from_seed(Default::default()) as &mut dyn CryptoRngCore; - let r1 = rng.next_u64(); - let rng: &mut dyn RngCore = rng.as_rngcore(); - let r2 = rng.next_u64(); - assert_ne!(r1, r2); + let mut rng1 = ChaChaRng::from_seed(Default::default()); + let rng2 = &mut rng1.clone() as &mut dyn CryptoRng; + for _ in 0..1000 { + assert_eq!(rng1.next_u64(), rng2.next_u64()); + } } } diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index f813784ff2f..5ad459d0fb4 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -43,7 +43,7 @@ //! } //! } //! -//! // optionally, also implement CryptoRng for MyRngCore +//! // optionally, also implement CryptoBlockRng for MyRngCore //! //! // Final RNG. //! let mut rng = BlockRng::::seed_from_u64(0); @@ -54,7 +54,7 @@ //! [`fill_bytes`]: RngCore::fill_bytes use crate::impls::{fill_via_u32_chunks, fill_via_u64_chunks}; -use crate::{CryptoRng, Error, RngCore, SeedableRng}; +use crate::{Error, CryptoRng, RngCore, SeedableRng}; use core::convert::AsRef; use core::fmt; #[cfg(feature = "serde1")] @@ -77,6 +77,12 @@ pub trait BlockRngCore { fn generate(&mut self, results: &mut Self::Results); } +/// A marker trait used to indicate that an [`RngCore`] implementation is +/// supposed to be cryptographically secure. +/// +/// See [`CryptoRng`][crate::CryptoRng] docs for more information. +pub trait CryptoBlockRng: BlockRngCore { } + /// A wrapper type implementing [`RngCore`] for some type implementing /// [`BlockRngCore`] with `u32` array buffer; i.e. this can be used to implement /// a full RNG from just a `generate` function. @@ -256,6 +262,8 @@ impl SeedableRng for BlockRng { } } +impl> CryptoRng for BlockRng {} + /// A wrapper type implementing [`RngCore`] for some type implementing /// [`BlockRngCore`] with `u64` array buffer; i.e. this can be used to implement /// a full RNG from just a `generate` function. @@ -422,7 +430,7 @@ impl SeedableRng for BlockRng64 { } } -impl CryptoRng for BlockRng {} +impl> CryptoRng for BlockRng64 {} #[cfg(test)] mod test { diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 70baf78dec4..9a6c0baa13f 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -191,8 +191,8 @@ pub trait RngCore { } } -/// A marker trait used to indicate that an [`RngCore`] or [`BlockRngCore`] -/// implementation is supposed to be cryptographically secure. +/// A marker trait used to indicate that an [`RngCore`] implementation is +/// supposed to be cryptographically secure. /// /// *Cryptographically secure generators*, also known as *CSPRNGs*, should /// satisfy an additional properties over other generators: given the first @@ -213,36 +213,7 @@ pub trait RngCore { /// weaknesses such as seeding from a weak entropy source or leaking state. /// /// [`BlockRngCore`]: block::BlockRngCore -pub trait CryptoRng {} - -/// An extension trait that is automatically implemented for any type -/// implementing [`RngCore`] and [`CryptoRng`]. -/// -/// It may be used as a trait object, and supports upcasting to [`RngCore`] via -/// the [`CryptoRngCore::as_rngcore`] method. -/// -/// # Example -/// -/// ``` -/// use rand_core::CryptoRngCore; -/// -/// #[allow(unused)] -/// fn make_token(rng: &mut dyn CryptoRngCore) -> [u8; 32] { -/// let mut buf = [0u8; 32]; -/// rng.fill_bytes(&mut buf); -/// buf -/// } -/// ``` -pub trait CryptoRngCore: CryptoRng + RngCore { - /// Upcast to an [`RngCore`] trait object. - fn as_rngcore(&mut self) -> &mut dyn RngCore; -} - -impl CryptoRngCore for T { - fn as_rngcore(&mut self) -> &mut dyn RngCore { - self - } -} +pub trait CryptoRng: RngCore {} /// A random number generator that can be explicitly seeded. /// diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index b78b850a4a6..a47ab7c7484 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -12,7 +12,7 @@ use core::mem::size_of; -use rand_core::block::{BlockRng, BlockRngCore}; +use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the @@ -147,8 +147,8 @@ where impl CryptoRng for ReseedingRng where - R: BlockRngCore + SeedableRng + CryptoRng, - Rsdr: RngCore + CryptoRng, + R: BlockRngCore + SeedableRng + CryptoBlockRng, + Rsdr: CryptoRng, { } @@ -276,10 +276,10 @@ where } } -impl CryptoRng for ReseedingCore +impl CryptoBlockRng for ReseedingCore where - R: BlockRngCore + SeedableRng + CryptoRng, - Rsdr: RngCore + CryptoRng, + R: BlockRngCore + SeedableRng + CryptoBlockRng, + Rsdr: CryptoRng, { } From 95b366ff5386aed51c4b77c3d3cb45739d7aef14 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 21 Feb 2023 12:18:05 +0000 Subject: [PATCH 285/443] Add uniform_float benchmark Note: sample_single and sample_single_inclusive use different code paths for sampling. --- Cargo.toml | 7 ++- benches/uniform_float.rs | 112 +++++++++++++++++++++++++++++++++++ src/distributions/uniform.rs | 3 +- 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 benches/uniform_float.rs diff --git a/Cargo.toml b/Cargo.toml index ec0e4d77676..911db289c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,4 +84,9 @@ harness = false [[bench]] name = "shuffle" path = "benches/shuffle.rs" -harness = false \ No newline at end of file +harness = false + +[[bench]] +name = "uniform_float" +path = "benches/uniform_float.rs" +harness = false diff --git a/benches/uniform_float.rs b/benches/uniform_float.rs new file mode 100644 index 00000000000..c67072b5bd1 --- /dev/null +++ b/benches/uniform_float.rs @@ -0,0 +1,112 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implement benchmarks for uniform distributions over FP types +//! +//! Sampling methods compared: +//! +//! - sample: current method: (x12 - 1.0) * (b - a) + a + +use core::time::Duration; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use rand::distributions::uniform::{SampleUniform, Uniform, UniformSampler}; +use rand::prelude::*; +use rand_chacha::ChaCha8Rng; +use rand_pcg::{Pcg32, Pcg64}; + +const WARM_UP_TIME: Duration = Duration::from_millis(1000); +const MEASUREMENT_TIME: Duration = Duration::from_secs(3); +const SAMPLE_SIZE: usize = 100_000; +const N_RESAMPLES: usize = 10_000; + +macro_rules! single_random { + ($name:literal, $R:ty, $T:ty, $f:ident, $g:expr) => { + $g.bench_function(BenchmarkId::new(stringify!($R), $name), |b| { + let mut rng = <$R>::from_entropy(); + let (mut low, mut high); + loop { + low = <$T>::from_bits(rng.gen()); + high = <$T>::from_bits(rng.gen()); + if (low < high) && (high - low).is_normal() { + break; + } + } + + b.iter(|| <$T as SampleUniform>::Sampler::$f(low, high, &mut rng)); + }); + }; + + ($R:ty, $T:ty, $g:expr) => { + single_random!("sample", $R, $T, sample_single, $g); + single_random!("sample_inclusive", $R, $T, sample_single_inclusive, $g); + }; + + ($c:expr, $T:ty) => {{ + let mut g = $c.benchmark_group(concat!("single_random_", stringify!($T))); + g.sample_size(SAMPLE_SIZE); + g.warm_up_time(WARM_UP_TIME); + g.measurement_time(MEASUREMENT_TIME); + g.nresamples(N_RESAMPLES); + single_random!(SmallRng, $T, g); + single_random!(ChaCha8Rng, $T, g); + single_random!(Pcg32, $T, g); + single_random!(Pcg64, $T, g); + g.finish(); + }}; +} + +fn single_random(c: &mut Criterion) { + single_random!(c, f32); + single_random!(c, f64); +} + +macro_rules! distr_random { + ($name:literal, $R:ty, $T:ty, $f:ident, $g:expr) => { + $g.bench_function(BenchmarkId::new(stringify!($R), $name), |b| { + let mut rng = <$R>::from_entropy(); + let dist = loop { + let low = <$T>::from_bits(rng.gen()); + let high = <$T>::from_bits(rng.gen()); + if let Ok(dist) = Uniform::<$T>::new_inclusive(low, high) { + break dist; + } + }; + + b.iter(|| <$T as SampleUniform>::Sampler::$f(&dist.0, &mut rng)); + }); + }; + + ($R:ty, $T:ty, $g:expr) => { + distr_random!("sample", $R, $T, sample, $g); + }; + + ($c:expr, $T:ty) => {{ + let mut g = $c.benchmark_group(concat!("distr_random_", stringify!($T))); + g.sample_size(SAMPLE_SIZE); + g.warm_up_time(WARM_UP_TIME); + g.measurement_time(MEASUREMENT_TIME); + g.nresamples(N_RESAMPLES); + distr_random!(SmallRng, $T, g); + distr_random!(ChaCha8Rng, $T, g); + distr_random!(Pcg32, $T, g); + distr_random!(Pcg64, $T, g); + g.finish(); + }}; +} + +fn distr_random(c: &mut Criterion) { + distr_random!(c, f32); + distr_random!(c, f64); +} + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = single_random, distr_random +} +criterion_main!(benches); diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index b4856ff6131..94e56ca6a1c 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -199,7 +199,8 @@ use serde::{Serialize, Deserialize}; #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] #[cfg_attr(feature = "serde1", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")))] -pub struct Uniform(X::Sampler); +// HACK: internals are public for benches +pub struct Uniform(pub X::Sampler); impl Uniform { /// Create a new `Uniform` instance, which samples uniformly from the half From 5e5879a5911aeda7eb4f8248dc6056ad64a3cb94 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 21 Feb 2023 12:58:50 +0000 Subject: [PATCH 286/443] Add direct impl of sample_single_inclusive for floats Around 20% faster for f32, slightly less for f64. --- src/distributions/uniform.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 94e56ca6a1c..34fc347658e 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -996,6 +996,38 @@ macro_rules! uniform_float_impl { } } } + + #[inline] + fn sample_single_inclusive(low_b: B1, high_b: B2, rng: &mut R) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + #[cfg(debug_assertions)] + if !low.all_finite() || !high.all_finite() { + return Err(Error::NonFinite); + } + if !low.all_le(high) { + return Err(Error::EmptyRange); + } + let scale = high - low; + if !scale.all_finite() { + return Err(Error::NonFinite); + } + + // Generate a value in the range [1, 2) + let value1_2 = + (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); + + // Get a value in the range [0, 1) to avoid overflow when multiplying by scale + let value0_1 = value1_2 - <$ty>::splat(1.0); + + // Doing multiply before addition allows some architectures + // to use a single instruction. + Ok(value0_1 * scale + low) + } } }; } From 7aedb36373a38d9e711029d48aa78724eb53e104 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 21 Feb 2023 15:42:35 +0000 Subject: [PATCH 287/443] Uniform for floats: extra tests --- src/distributions/uniform.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 34fc347658e..28d78075f8c 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1400,6 +1400,9 @@ mod tests { let v = <$ty as SampleUniform>::Sampler ::sample_single(low, high, &mut rng).unwrap().extract(lane); assert!(low_scalar <= v && v < high_scalar); + let v = <$ty as SampleUniform>::Sampler + ::sample_single_inclusive(low, high, &mut rng).unwrap().extract(lane); + assert!(low_scalar <= v && v <= high_scalar); } assert_eq!( @@ -1412,8 +1415,19 @@ mod tests { assert_eq!(<$ty as SampleUniform>::Sampler ::sample_single(low, high, &mut zero_rng).unwrap() .extract(lane), low_scalar); + assert_eq!(<$ty as SampleUniform>::Sampler + ::sample_single_inclusive(low, high, &mut zero_rng).unwrap() + .extract(lane), low_scalar); + assert!(max_rng.sample(my_uniform).extract(lane) < high_scalar); assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar); + // sample_single cannot cope with max_rng: + // assert!(<$ty as SampleUniform>::Sampler + // ::sample_single(low, high, &mut max_rng).unwrap() + // .extract(lane) < high_scalar); + assert!(<$ty as SampleUniform>::Sampler + ::sample_single_inclusive(low, high, &mut max_rng).unwrap() + .extract(lane) <= high_scalar); // Don't run this test for really tiny differences between high and low // since for those rounding might result in selecting high for a very From 17c8b2625ceef3f958087e8b10a4d19ddd53bcc5 Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 27 Feb 2023 00:07:03 -0800 Subject: [PATCH 288/443] Don't run the random write test (#1294) --- rand_core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 9a6c0baa13f..a5a7fb150c6 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -451,7 +451,7 @@ impl RngCore for Box { /// /// # Examples /// -/// ```rust +/// ```no_run /// # use std::{io, io::Read}; /// # use std::fs::File; /// # use rand_core::{OsRng, RngCore}; From 0f3ecedef0ea4445a38493fa3d4173b49b3574f9 Mon Sep 17 00:00:00 2001 From: Thomas Dupic <32767097+Thopic@users.noreply.github.com> Date: Mon, 27 Feb 2023 03:08:13 -0500 Subject: [PATCH 289/443] =?UTF-8?q?Poisson=20distribution=20falls=20into?= =?UTF-8?q?=20an=20infinite=20loop=20for=20parameter=20=CE=BB=3D=E2=88=9E?= =?UTF-8?q?=20(#1290)=20(#1291)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rand_distr/CHANGELOG.md | 1 + rand_distr/src/poisson.rs | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 50cd913b21f..d1da24a5a74 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This breaks serialization compatibility with older versions. - Upgrade Rand - Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda +- Fix `Poisson` distribution instantiation so it return an error if lambda is infinite ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index b0b3d15a8ae..199313a28a1 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -44,14 +44,17 @@ where F: Float + FloatConst, Standard: Distribution /// Error type returned from `Poisson::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { - /// `lambda <= 0` or `nan`. + /// `lambda <= 0` ShapeTooSmall, + /// `lambda = ∞` or `lambda = nan` + NonFinite, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Error::ShapeTooSmall => "lambda is not positive in Poisson distribution", + Error::NonFinite => "lambda is infinite or nan in Poisson distribution", }) } } @@ -66,6 +69,9 @@ where F: Float + FloatConst, Standard: Distribution /// Construct a new `Poisson` with the given shape parameter /// `lambda`. pub fn new(lambda: F) -> Result, Error> { + if !lambda.is_finite() { + return Err(Error::NonFinite); + } if !(lambda > F::zero()) { return Err(Error::ShapeTooSmall); } @@ -163,7 +169,7 @@ mod test { fn test_poisson_avg() { test_poisson_avg_gen::(10.0, 0.1); test_poisson_avg_gen::(15.0, 0.1); - + test_poisson_avg_gen::(10.0, 0.1); test_poisson_avg_gen::(15.0, 0.1); @@ -178,6 +184,12 @@ mod test { Poisson::new(0.0).unwrap(); } + #[test] + #[should_panic] + fn test_poisson_invalid_lambda_infinity() { + Poisson::new(f64::INFINITY).unwrap(); + } + #[test] #[should_panic] fn test_poisson_invalid_lambda_neg() { @@ -188,4 +200,4 @@ mod test { fn poisson_distributions_can_be_compared() { assert_eq!(Poisson::new(1.0), Poisson::new(1.0)); } -} \ No newline at end of file +} From 4ecb35eaf0723ef0e5e39aa559f0ca887d9e248e Mon Sep 17 00:00:00 2001 From: warren Date: Tue, 28 Feb 2023 16:12:08 -0500 Subject: [PATCH 290/443] Refactor: * Create a struct for each random variate generation method: DirichletFromGamma and DirichletFromBeta. * Move the initialization of the data required by the generators into the new() method. * Make the main Dirichlet object an enum. --- rand_distr/src/dirichlet.rs | 238 +++++++++++++++++++++++++----------- 1 file changed, 170 insertions(+), 68 deletions(-) diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 602bb861e0a..414d694e7cb 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -9,11 +9,144 @@ //! The dirichlet distribution. #![cfg(feature = "alloc")] -use num_traits::{Float, NumCast}; use crate::{Beta, Distribution, Exp1, Gamma, Open01, StandardNormal}; -use rand::Rng; -use core::fmt; use alloc::{boxed::Box, vec, vec::Vec}; +use core::fmt; +use num_traits::{Float, NumCast}; +use rand::Rng; + +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct DirichletFromGamma +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + samplers: Box<[Gamma]>, +} + +impl DirichletFromGamma +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + // Construct a new `Dirichlet` with the given alpha parameter `alpha`. + // + // This function is part of a private implementation detail. + // It assumes that the input is correct, so no validation is done. + #[inline] + fn new(alpha: &[F]) -> Dirichlet { + let gamma_dists = alpha + .iter() + .map(|a| Gamma::new(*a, F::one()).unwrap()) + .collect::>>() + .into_boxed_slice(); + Dirichlet::FromGamma(DirichletFromGamma { + samplers: gamma_dists, + }) + } +} + +impl Distribution> for DirichletFromGamma +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + fn sample(&self, rng: &mut R) -> Vec { + let n = self.samplers.len(); + let mut samples = vec![F::zero(); n]; + let mut sum = F::zero(); + for (s, g) in samples.iter_mut().zip(self.samplers.iter()) { + *s = g.sample(rng); + sum = sum + (*s); + } + let invacc = F::one() / sum; + for s in samples.iter_mut() { + *s = (*s) * invacc; + } + samples + } +} + +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct DirichletFromBeta +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + samplers: Box<[Beta]>, +} + +impl DirichletFromBeta +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + // Construct a new `Dirichlet` with the given alpha parameter `alpha`. + // + // This function is part of a private implementation detail. + // It assumes that the input is correct, so no validation is done. + #[inline] + fn new(alpha: &[F]) -> Dirichlet { + // Form the right-to-left cumulative sum of alpha, exluding the + // first element of alpha. E.g. if alpha = [a0, a1, a2, a3], then + // after the call to `alpha_sum_rl.reverse()` below, alpha_sum_rl + // will hold [a1+a2+a3, a2+a3, a3]. + let mut alpha_sum_rl: Vec = alpha + .iter() + .skip(1) + .rev() + // scan does the cumulative sum + .scan(F::zero(), |sum, x| { + *sum = *sum + *x; + Some(*sum) + }) + .collect(); + alpha_sum_rl.reverse(); + let beta_dists = alpha + .iter() + .zip(alpha_sum_rl.iter()) + .map(|t| Beta::new(*t.0, *t.1).unwrap()) + .collect::>>() + .into_boxed_slice(); + Dirichlet::FromBeta(DirichletFromBeta { + samplers: beta_dists, + }) + } +} + +impl Distribution> for DirichletFromBeta +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + fn sample(&self, rng: &mut R) -> Vec { + let n = self.samplers.len(); + let mut samples = vec![F::zero(); n + 1]; + let mut acc = F::one(); + + for (s, beta) in samples.iter_mut().zip(self.samplers.iter()) { + let beta_sample = beta.sample(rng); + *s = acc * beta_sample; + acc = acc * (F::one() - beta_sample); + } + samples[n] = acc; + samples + } +} /// The Dirichlet distribution `Dirichlet(alpha)`. /// @@ -34,15 +167,18 @@ use alloc::{boxed::Box, vec, vec::Vec}; #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Dirichlet +pub enum Dirichlet where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - /// Concentration parameters (alpha) - alpha: Box<[F]>, + /// Dirichlet distribution that generates samples using the gamma distribution. + FromGamma(DirichletFromGamma), + + /// Dirichlet distribution that generates samples using the beta distribution. + FromBeta(DirichletFromBeta), } /// Error type returned from `Dirchlet::new`. @@ -81,7 +217,7 @@ where { /// Construct a new `Dirichlet` with the given alpha parameter `alpha`. /// - /// Requires `alpha.len() >= 2`. + /// Requires `alpha.len() >= 2`, and each value in `alpha` must be positive. #[inline] pub fn new(alpha: &[F]) -> Result, Error> { if alpha.len() < 2 { @@ -93,12 +229,17 @@ where } } - Ok(Dirichlet { alpha: alpha.to_vec().into_boxed_slice() }) + if alpha.iter().all(|x| *x <= NumCast::from(0.1).unwrap()) { + // All the values in alpha are less than 0.1. + Ok(DirichletFromBeta::new(alpha)) + } else { + Ok(DirichletFromGamma::new(alpha)) + } } /// Construct a new `Dirichlet` with the given shape parameter `alpha` and `size`. /// - /// Requires `size >= 2`. + /// Requires `alpha > 0` and `size >= 2`. #[inline] pub fn new_with_size(alpha: F, size: usize) -> Result, Error> { if !(alpha > F::zero()) { @@ -107,9 +248,7 @@ where if size < 2 { return Err(Error::SizeTooSmall); } - Ok(Dirichlet { - alpha: vec![alpha; size].into_boxed_slice(), - }) + Ok(Dirichlet::new(vec![alpha; size].as_slice()).unwrap()) } } @@ -121,60 +260,10 @@ where Open01: Distribution, { fn sample(&self, rng: &mut R) -> Vec { - let n = self.alpha.len(); - let mut samples = vec![F::zero(); n]; - - if self.alpha.iter().all(|x| *x <= NumCast::from(0.1).unwrap()) { - // All the values in alpha are less than 0.1. - // - // When all the alpha parameters are sufficiently small, there - // is a nontrivial probability that the samples from the gamma - // distributions used in the other method will all be 0, which - // results in the dirichlet samples being nan. So instead of - // use that method, use the "stick breaking" method based on the - // marginal beta distributions. - // - // Form the right-to-left cumulative sum of alpha, exluding the - // first element of alpha. E.g. if alpha = [a0, a1, a2, a3], then - // after the call to `alpha_sum_rl.reverse()` below, alpha_sum_rl - // will hold [a1+a2+a3, a2+a3, a3]. - let mut alpha_sum_rl: Vec = self - .alpha - .iter() - .skip(1) - .rev() - // scan does the cumulative sum - .scan(F::zero(), |sum, x| { - *sum = *sum + *x; - Some(*sum) - }) - .collect(); - alpha_sum_rl.reverse(); - let mut acc = F::one(); - for ((s, &a), &b) in samples - .iter_mut() - .zip(self.alpha.iter()) - .zip(alpha_sum_rl.iter()) - { - let beta = Beta::new(a, b).unwrap(); - let beta_sample = beta.sample(rng); - *s = acc * beta_sample; - acc = acc * (F::one() - beta_sample); - } - samples[n - 1] = acc; - } else { - let mut sum = F::zero(); - for (s, &a) in samples.iter_mut().zip(self.alpha.iter()) { - let g = Gamma::new(a, F::one()).unwrap(); - *s = g.sample(rng); - sum = sum + (*s); - } - let invacc = F::one() / sum; - for s in samples.iter_mut() { - *s = (*s) * invacc; - } + match self { + Dirichlet::FromGamma(dirichlet) => dirichlet.sample(rng), + Dirichlet::FromBeta(dirichlet) => dirichlet.sample(rng), } - samples } } @@ -192,7 +281,7 @@ mod test { // fn check_dirichlet_means(alpha: &Vec, n: i32, rtol: f64, seed: u64) { let d = Dirichlet::new(&alpha).unwrap(); - let alpha_len = d.alpha.len(); + let alpha_len = alpha.len(); let mut rng = crate::test::rng(seed); let mut sums = vec![0.0; alpha_len]; for _ in 0..n { @@ -202,8 +291,8 @@ mod test { } } let sample_mean: Vec = sums.iter().map(|x| x / n as f64).collect(); - let alpha_sum: f64 = d.alpha.iter().sum(); - let expected_mean: Vec = d.alpha.iter().map(|x| x / alpha_sum).collect(); + let alpha_sum: f64 = alpha.iter().sum(); + let expected_mean: Vec = alpha.iter().map(|x| x / alpha_sum).collect(); for i in 0..alpha_len { assert_almost_eq!(sample_mean[i], expected_mean[i], rtol); } @@ -287,12 +376,25 @@ mod test { Dirichlet::new_with_size(0.5f64, 1).unwrap(); } + #[test] + #[should_panic] + fn test_dirichlet_invalid_length_slice() { + Dirichlet::new(&[0.25]).unwrap(); + } + #[test] #[should_panic] fn test_dirichlet_invalid_alpha() { Dirichlet::new_with_size(0.0f64, 2).unwrap(); } + #[test] + #[should_panic] + fn test_dirichlet_invalid_alpha_slice() { + // 0 in alpha must result in a panic. + Dirichlet::new(&[0.1f64, 0.0f64, 1.5f64]).unwrap(); + } + #[test] fn dirichlet_distributions_can_be_compared() { assert_eq!(Dirichlet::new(&[1.0, 2.0]), Dirichlet::new(&[1.0, 2.0])); From 7513e838b7fc081ce59a55a4aca60284c193309e Mon Sep 17 00:00:00 2001 From: warren Date: Tue, 28 Feb 2023 20:01:42 -0500 Subject: [PATCH 291/443] Convert `Dirichlet` from an `enum` to a `struct` containing an `enum`. --- rand_distr/src/dirichlet.rs | 54 ++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 414d694e7cb..825812d5d9f 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -17,7 +17,7 @@ use rand::Rng; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct DirichletFromGamma +struct DirichletFromGamma where F: Float, StandardNormal: Distribution, @@ -39,15 +39,15 @@ where // This function is part of a private implementation detail. // It assumes that the input is correct, so no validation is done. #[inline] - fn new(alpha: &[F]) -> Dirichlet { + fn new(alpha: &[F]) -> DirichletFromGamma { let gamma_dists = alpha .iter() .map(|a| Gamma::new(*a, F::one()).unwrap()) .collect::>>() .into_boxed_slice(); - Dirichlet::FromGamma(DirichletFromGamma { + DirichletFromGamma { samplers: gamma_dists, - }) + } } } @@ -76,7 +76,7 @@ where #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct DirichletFromBeta +struct DirichletFromBeta where F: Float, StandardNormal: Distribution, @@ -98,7 +98,7 @@ where // This function is part of a private implementation detail. // It assumes that the input is correct, so no validation is done. #[inline] - fn new(alpha: &[F]) -> Dirichlet { + fn new(alpha: &[F]) -> DirichletFromBeta { // Form the right-to-left cumulative sum of alpha, exluding the // first element of alpha. E.g. if alpha = [a0, a1, a2, a3], then // after the call to `alpha_sum_rl.reverse()` below, alpha_sum_rl @@ -120,9 +120,9 @@ where .map(|t| Beta::new(*t.0, *t.1).unwrap()) .collect::>>() .into_boxed_slice(); - Dirichlet::FromBeta(DirichletFromBeta { + DirichletFromBeta { samplers: beta_dists, - }) + } } } @@ -148,6 +148,22 @@ where } } +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +enum DirichletRepr +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + /// Dirichlet distribution that generates samples using the gamma distribution. + FromGamma(DirichletFromGamma), + + /// Dirichlet distribution that generates samples using the beta distribution. + FromBeta(DirichletFromBeta), +} + /// The Dirichlet distribution `Dirichlet(alpha)`. /// /// The Dirichlet distribution is a family of continuous multivariate @@ -167,18 +183,14 @@ where #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub enum Dirichlet +pub struct Dirichlet where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - /// Dirichlet distribution that generates samples using the gamma distribution. - FromGamma(DirichletFromGamma), - - /// Dirichlet distribution that generates samples using the beta distribution. - FromBeta(DirichletFromBeta), + repr: DirichletRepr, } /// Error type returned from `Dirchlet::new`. @@ -231,9 +243,13 @@ where if alpha.iter().all(|x| *x <= NumCast::from(0.1).unwrap()) { // All the values in alpha are less than 0.1. - Ok(DirichletFromBeta::new(alpha)) + Ok(Dirichlet { + repr: DirichletRepr::FromBeta(DirichletFromBeta::new(alpha)), + }) } else { - Ok(DirichletFromGamma::new(alpha)) + Ok(Dirichlet { + repr: DirichletRepr::FromGamma(DirichletFromGamma::new(alpha)), + }) } } @@ -260,9 +276,9 @@ where Open01: Distribution, { fn sample(&self, rng: &mut R) -> Vec { - match self { - Dirichlet::FromGamma(dirichlet) => dirichlet.sample(rng), - Dirichlet::FromBeta(dirichlet) => dirichlet.sample(rng), + match &self.repr { + DirichletRepr::FromGamma(dirichlet) => dirichlet.sample(rng), + DirichletRepr::FromBeta(dirichlet) => dirichlet.sample(rng), } } } From 030d2ad4f4748d35ef03ad0de4b71506f9722123 Mon Sep 17 00:00:00 2001 From: warren Date: Tue, 28 Feb 2023 20:54:28 -0500 Subject: [PATCH 292/443] Fix namespace qualifier for Serialize and Deserialize. --- rand_distr/src/dirichlet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 825812d5d9f..882228eed16 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -149,7 +149,7 @@ where } #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] enum DirichletRepr where F: Float, From 0f5af6672dd65827d64ad5efea6042de8a4aeb04 Mon Sep 17 00:00:00 2001 From: Virgile Andreani Date: Sat, 18 Mar 2023 05:21:59 -0400 Subject: [PATCH 293/443] Use const generics in Dirichlet (#1292) * Use const generics in Dirichlet * Serialize const arrays with serde_with --- rand_distr/CHANGELOG.md | 2 + rand_distr/Cargo.toml | 1 + rand_distr/src/dirichlet.rs | 68 ++++++++--------------------- rand_distr/tests/value_stability.rs | 6 +-- 4 files changed, 25 insertions(+), 52 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index d1da24a5a74..7d6c0602e17 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Upgrade Rand - Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda - Fix `Poisson` distribution instantiation so it return an error if lambda is infinite +- `Dirichlet` now uses `const` generics, which means that its size is required at compile time (#1292) +- The `Dirichlet::new_with_size` constructor was removed (#1292) ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index ff1400d6e6e..49fd2e6f8a9 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -27,6 +27,7 @@ serde1 = ["serde", "rand/serde1"] rand = { path = "..", version = "0.9.0", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } +serde_with = { version = "1.14.0", optional = true } [dev-dependencies] rand_pcg = { version = "0.4.0", path = "../rand_pcg" } diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 786cbccd0cc..526480411df 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -13,7 +13,8 @@ use num_traits::Float; use crate::{Distribution, Exp1, Gamma, Open01, StandardNormal}; use rand::Rng; use core::fmt; -use alloc::{boxed::Box, vec, vec::Vec}; +#[cfg(feature = "serde_with")] +use serde_with::serde_as; /// The Dirichlet distribution `Dirichlet(alpha)`. /// @@ -27,14 +28,14 @@ use alloc::{boxed::Box, vec, vec::Vec}; /// use rand::prelude::*; /// use rand_distr::Dirichlet; /// -/// let dirichlet = Dirichlet::new(&[1.0, 2.0, 3.0]).unwrap(); +/// let dirichlet = Dirichlet::new([1.0, 2.0, 3.0]).unwrap(); /// let samples = dirichlet.sample(&mut rand::thread_rng()); /// println!("{:?} is from a Dirichlet([1.0, 2.0, 3.0]) distribution", samples); /// ``` #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +#[cfg_attr(feature = "serde_with", serde_as)] #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Dirichlet +pub struct Dirichlet where F: Float, StandardNormal: Distribution, @@ -42,7 +43,8 @@ where Open01: Distribution, { /// Concentration parameters (alpha) - alpha: Box<[F]>, + #[cfg_attr(feature = "serde_with", serde_as(as = "[_; N]"))] + alpha: [F; N], } /// Error type returned from `Dirchlet::new`. @@ -72,7 +74,7 @@ impl fmt::Display for Error { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} -impl Dirichlet +impl Dirichlet where F: Float, StandardNormal: Distribution, @@ -83,8 +85,8 @@ where /// /// Requires `alpha.len() >= 2`. #[inline] - pub fn new(alpha: &[F]) -> Result, Error> { - if alpha.len() < 2 { + pub fn new(alpha: [F; N]) -> Result, Error> { + if N < 2 { return Err(Error::AlphaTooShort); } for &ai in alpha.iter() { @@ -93,36 +95,19 @@ where } } - Ok(Dirichlet { alpha: alpha.to_vec().into_boxed_slice() }) - } - - /// Construct a new `Dirichlet` with the given shape parameter `alpha` and `size`. - /// - /// Requires `size >= 2`. - #[inline] - pub fn new_with_size(alpha: F, size: usize) -> Result, Error> { - if !(alpha > F::zero()) { - return Err(Error::AlphaTooSmall); - } - if size < 2 { - return Err(Error::SizeTooSmall); - } - Ok(Dirichlet { - alpha: vec![alpha; size].into_boxed_slice(), - }) + Ok(Dirichlet { alpha }) } } -impl Distribution> for Dirichlet +impl Distribution<[F; N]> for Dirichlet where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - fn sample(&self, rng: &mut R) -> Vec { - let n = self.alpha.len(); - let mut samples = vec![F::zero(); n]; + fn sample(&self, rng: &mut R) -> [F; N] { + let mut samples = [F::zero(); N]; let mut sum = F::zero(); for (s, &a) in samples.iter_mut().zip(self.alpha.iter()) { @@ -140,27 +125,12 @@ where #[cfg(test)] mod test { + use alloc::vec::Vec; use super::*; #[test] fn test_dirichlet() { - let d = Dirichlet::new(&[1.0, 2.0, 3.0]).unwrap(); - let mut rng = crate::test::rng(221); - let samples = d.sample(&mut rng); - let _: Vec = samples - .into_iter() - .map(|x| { - assert!(x > 0.0); - x - }) - .collect(); - } - - #[test] - fn test_dirichlet_with_param() { - let alpha = 0.5f64; - let size = 2; - let d = Dirichlet::new_with_size(alpha, size).unwrap(); + let d = Dirichlet::new([1.0, 2.0, 3.0]).unwrap(); let mut rng = crate::test::rng(221); let samples = d.sample(&mut rng); let _: Vec = samples @@ -175,17 +145,17 @@ mod test { #[test] #[should_panic] fn test_dirichlet_invalid_length() { - Dirichlet::new_with_size(0.5f64, 1).unwrap(); + Dirichlet::new([0.5]).unwrap(); } #[test] #[should_panic] fn test_dirichlet_invalid_alpha() { - Dirichlet::new_with_size(0.0f64, 2).unwrap(); + Dirichlet::new([0.1, 0.0, 0.3]).unwrap(); } #[test] fn dirichlet_distributions_can_be_compared() { - assert_eq!(Dirichlet::new(&[1.0, 2.0]), Dirichlet::new(&[1.0, 2.0])); + assert_eq!(Dirichlet::new([1.0, 2.0]), Dirichlet::new([1.0, 2.0])); } } diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index d3754705db5..4b9490a6581 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -348,10 +348,10 @@ fn weibull_stability() { fn dirichlet_stability() { let mut rng = get_rng(223); assert_eq!( - rng.sample(Dirichlet::new(&[1.0, 2.0, 3.0]).unwrap()), - vec![0.12941567177708177, 0.4702121891675036, 0.4003721390554146] + rng.sample(Dirichlet::new([1.0, 2.0, 3.0]).unwrap()), + [0.12941567177708177, 0.4702121891675036, 0.4003721390554146] ); - assert_eq!(rng.sample(Dirichlet::new_with_size(8.0, 5).unwrap()), vec![ + assert_eq!(rng.sample(Dirichlet::new([8.0; 5]).unwrap()), [ 0.17684200044809556, 0.29915953935953055, 0.1832858056608014, From 8d98d4531d3d076f38f375ea87319ef23b4a0985 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 18 Mar 2023 09:43:31 +0000 Subject: [PATCH 294/443] Bench uniform_float: better grouping of plots Also avoid using internal API of Uniform distribution. --- benches/uniform_float.rs | 27 +++++++++------------------ src/distributions/uniform.rs | 3 +-- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/benches/uniform_float.rs b/benches/uniform_float.rs index c67072b5bd1..3366ad3f986 100644 --- a/benches/uniform_float.rs +++ b/benches/uniform_float.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Developers of the Rand project. +// Copyright 2023 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -25,8 +25,8 @@ const SAMPLE_SIZE: usize = 100_000; const N_RESAMPLES: usize = 10_000; macro_rules! single_random { - ($name:literal, $R:ty, $T:ty, $f:ident, $g:expr) => { - $g.bench_function(BenchmarkId::new(stringify!($R), $name), |b| { + ($R:ty, $T:ty, $g:expr) => { + $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { let mut rng = <$R>::from_entropy(); let (mut low, mut high); loop { @@ -37,17 +37,12 @@ macro_rules! single_random { } } - b.iter(|| <$T as SampleUniform>::Sampler::$f(low, high, &mut rng)); + b.iter(|| <$T as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng)); }); }; - ($R:ty, $T:ty, $g:expr) => { - single_random!("sample", $R, $T, sample_single, $g); - single_random!("sample_inclusive", $R, $T, sample_single_inclusive, $g); - }; - ($c:expr, $T:ty) => {{ - let mut g = $c.benchmark_group(concat!("single_random_", stringify!($T))); + let mut g = $c.benchmark_group("uniform_single"); g.sample_size(SAMPLE_SIZE); g.warm_up_time(WARM_UP_TIME); g.measurement_time(MEASUREMENT_TIME); @@ -66,8 +61,8 @@ fn single_random(c: &mut Criterion) { } macro_rules! distr_random { - ($name:literal, $R:ty, $T:ty, $f:ident, $g:expr) => { - $g.bench_function(BenchmarkId::new(stringify!($R), $name), |b| { + ($R:ty, $T:ty, $g:expr) => { + $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { let mut rng = <$R>::from_entropy(); let dist = loop { let low = <$T>::from_bits(rng.gen()); @@ -77,16 +72,12 @@ macro_rules! distr_random { } }; - b.iter(|| <$T as SampleUniform>::Sampler::$f(&dist.0, &mut rng)); + b.iter(|| dist.sample(&mut rng)); }); }; - ($R:ty, $T:ty, $g:expr) => { - distr_random!("sample", $R, $T, sample, $g); - }; - ($c:expr, $T:ty) => {{ - let mut g = $c.benchmark_group(concat!("distr_random_", stringify!($T))); + let mut g = $c.benchmark_group("uniform_distribution"); g.sample_size(SAMPLE_SIZE); g.warm_up_time(WARM_UP_TIME); g.measurement_time(MEASUREMENT_TIME); diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 28d78075f8c..e2fcd61f678 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -199,8 +199,7 @@ use serde::{Serialize, Deserialize}; #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] #[cfg_attr(feature = "serde1", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")))] -// HACK: internals are public for benches -pub struct Uniform(pub X::Sampler); +pub struct Uniform(X::Sampler); impl Uniform { /// Create a new `Uniform` instance, which samples uniformly from the half From ee1653c35fbff1435ef9b7f2a62b44eade2f42d6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 18 Mar 2023 10:05:27 +0000 Subject: [PATCH 295/443] CI: benches require small_rng --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a35e82db3df..262fb9d88ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,7 +79,7 @@ jobs: run: | cargo test --target ${{ matrix.target }} --features=nightly cargo test --target ${{ matrix.target }} --all-features - cargo test --target ${{ matrix.target }} --benches --features=nightly + cargo test --target ${{ matrix.target }} --benches --features=nightly,small_rng cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - name: Test rand From 145c6a2b82686013ee546101c836edff81cfcbd9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 23 Mar 2023 10:36:48 +0000 Subject: [PATCH 296/443] Re-introduce Rng::gen_iter --- src/rng.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/rng.rs b/src/rng.rs index 1b53298d511..3c4b7f870a5 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -133,6 +133,32 @@ pub trait Rng: RngCore { range.sample_single(self).unwrap() } + /// Generate values via an iterator + /// + /// This is a just a wrapper over [`Rng::sample_iter`] using + /// [`distributions::Standard`]. + /// + /// Note: this method consumes its argument. Use + /// `(&mut rng).gen_iter()` to avoid consuming the RNG. + /// + /// # Example + /// + /// ``` + /// use rand::{rngs::mock::StepRng, Rng}; + /// + /// let rng = StepRng::new(1, 1); + /// let v: Vec = rng.gen_iter().take(5).collect(); + /// assert_eq!(&v, &[1, 2, 3, 4, 5]); + /// ``` + #[inline] + fn gen_iter(self) -> distributions::DistIter + where + Self: Sized, + Standard: Distribution, + { + Standard.sample_iter(self) + } + /// Sample a new value, using the given distribution. /// /// ### Example @@ -153,11 +179,8 @@ pub trait Rng: RngCore { /// Create an iterator that generates values using the given distribution. /// - /// Note that this function takes its arguments by value. This works since - /// `(&mut R): Rng where R: Rng` and - /// `(&D): Distribution where D: Distribution`, - /// however borrowing is not automatic hence `rng.sample_iter(...)` may - /// need to be replaced with `(&mut rng).sample_iter(...)`. + /// Note: this method consumes its arguments. Use + /// `(&mut rng).sample_iter(..)` to avoid consuming the RNG. /// /// # Example /// From 22d0756b68f4b7c9f7a68a06e39d06354009603e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 24 Mar 2023 12:29:15 +0000 Subject: [PATCH 297/443] Uniform sampling: use Canon's method, Lemire's method (#1287) Also: * Add uniform distribution benchmarks * Add "unbiased" feature flag * Fix feature simd_support * Uniform: impl PartialEq, Eq where possible * CI: benches now require small_rng; build-test unbiased --- .github/workflows/test.yml | 4 +- CHANGELOG.md | 1 + Cargo.toml | 11 +- benches/uniform.rs | 78 ++++++++++++++ src/distributions/uniform.rs | 193 +++++++++++++++++++---------------- src/seq/index.rs | 10 +- src/seq/mod.rs | 38 +++---- 7 files changed, 215 insertions(+), 120 deletions(-) create mode 100644 benches/uniform.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a35e82db3df..14639f24d38 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,13 +79,13 @@ jobs: run: | cargo test --target ${{ matrix.target }} --features=nightly cargo test --target ${{ matrix.target }} --all-features - cargo test --target ${{ matrix.target }} --benches --features=nightly + cargo test --target ${{ matrix.target }} --benches --features=small_rng,nightly cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - name: Test rand run: | cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng + cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng,unbiased cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng cargo test --target ${{ matrix.target }} --examples - name: Test rand (all stable features) diff --git a/CHANGELOG.md b/CHANGELOG.md index 083b7dbbeb4..8706df8c639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ### Distributions - `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229) - `Uniform` implements `TryFrom` instead of `From` for ranges (#1229) +- `Uniform` now uses Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287) ### Other - Simpler and faster implementation of Floyd's F2 (#1277). This diff --git a/Cargo.toml b/Cargo.toml index ec0e4d77676..f85e126d739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,11 @@ std_rng = ["rand_chacha"] # Option: enable SmallRng small_rng = [] +# Option: use unbiased sampling for algorithms supporting this option: Uniform distribution. +# By default, bias affecting no more than one in 2^48 samples is accepted. +# Note: enabling this option is expected to affect reproducibility of results. +unbiased = [] + [workspace] members = [ "rand_core", @@ -76,6 +81,10 @@ bincode = "1.2.1" rayon = "1.5.3" criterion = { version = "0.4" } +[[bench]] +name = "uniform" +harness = false + [[bench]] name = "seq_choose" path = "benches/seq_choose.rs" @@ -84,4 +93,4 @@ harness = false [[bench]] name = "shuffle" path = "benches/shuffle.rs" -harness = false \ No newline at end of file +harness = false diff --git a/benches/uniform.rs b/benches/uniform.rs new file mode 100644 index 00000000000..d0128d5a48e --- /dev/null +++ b/benches/uniform.rs @@ -0,0 +1,78 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implement benchmarks for uniform distributions over integer types + +use core::time::Duration; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use rand::distributions::uniform::{SampleRange, Uniform}; +use rand::prelude::*; +use rand_chacha::ChaCha8Rng; +use rand_pcg::{Pcg32, Pcg64}; + +const WARM_UP_TIME: Duration = Duration::from_millis(1000); +const MEASUREMENT_TIME: Duration = Duration::from_secs(3); +const SAMPLE_SIZE: usize = 100_000; +const N_RESAMPLES: usize = 10_000; + +macro_rules! sample { + ($R:ty, $T:ty, $U:ty, $g:expr) => { + $g.bench_function(BenchmarkId::new(stringify!($R), "single"), |b| { + let mut rng = <$R>::from_entropy(); + let x = rng.gen::<$U>(); + let bits = (<$T>::BITS / 2); + let mask = (1 as $U).wrapping_neg() >> bits; + let range = (x >> bits) * (x & mask); + let low = <$T>::MIN; + let high = low.wrapping_add(range as $T); + + b.iter(|| (low..=high).sample_single(&mut rng)); + }); + + $g.bench_function(BenchmarkId::new(stringify!($R), "distr"), |b| { + let mut rng = <$R>::from_entropy(); + let x = rng.gen::<$U>(); + let bits = (<$T>::BITS / 2); + let mask = (1 as $U).wrapping_neg() >> bits; + let range = (x >> bits) * (x & mask); + let low = <$T>::MIN; + let high = low.wrapping_add(range as $T); + let dist = Uniform::<$T>::new_inclusive(<$T>::MIN, high).unwrap(); + + b.iter(|| dist.sample(&mut rng)); + }); + }; + + ($c:expr, $T:ty, $U:ty) => {{ + let mut g = $c.benchmark_group(concat!("sample", stringify!($T))); + g.sample_size(SAMPLE_SIZE); + g.warm_up_time(WARM_UP_TIME); + g.measurement_time(MEASUREMENT_TIME); + g.nresamples(N_RESAMPLES); + sample!(SmallRng, $T, $U, g); + sample!(ChaCha8Rng, $T, $U, g); + sample!(Pcg32, $T, $U, g); + sample!(Pcg64, $T, $U, g); + g.finish(); + }}; +} + +fn sample(c: &mut Criterion) { + sample!(c, i8, u8); + sample!(c, i16, u16); + sample!(c, i32, u32); + sample!(c, i64, u64); + sample!(c, i128, u128); +} + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = sample +} +criterion_main!(benches); diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index b4856ff6131..326d1ed4e86 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -195,7 +195,7 @@ use serde::{Serialize, Deserialize}; /// [`new`]: Uniform::new /// [`new_inclusive`]: Uniform::new_inclusive /// [`Rng::gen_range`]: Rng::gen_range -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] #[cfg_attr(feature = "serde1", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")))] @@ -444,21 +444,21 @@ impl SampleRange for RangeInclusive { /// use `u32` for our `zone` and samples (because it's not slower and because /// it reduces the chance of having to reject a sample). In this case we cannot /// store `zone` in the target type since it is too large, however we know -/// `ints_to_reject < range <= $unsigned::MAX`. +/// `ints_to_reject < range <= $uty::MAX`. /// /// An alternative to using a modulus is widening multiply: After a widening /// multiply by `range`, the result is in the high word. Then comparing the low /// word against `zone` makes sure our distribution is uniform. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UniformInt { low: X, range: X, - z: X, // either ints_to_reject or zone depending on implementation + thresh: X, // effectively 2.pow(max(64, uty_bits)) % range } macro_rules! uniform_int_impl { - ($ty:ty, $unsigned:ident, $u_large:ident) => { + ($ty:ty, $uty:ty, $sample_ty:ident) => { impl SampleUniform for $ty { type Sampler = UniformInt<$ty>; } @@ -466,7 +466,7 @@ macro_rules! uniform_int_impl { impl UniformSampler for UniformInt<$ty> { // We play free and fast with unsigned vs signed here // (when $ty is signed), but that's fine, since the - // contract of this macro is for $ty and $unsigned to be + // contract of this macro is for $ty and $uty to be // "bit-equal", so casting between them is a no-op. type X = $ty; @@ -498,41 +498,38 @@ macro_rules! uniform_int_impl { if !(low <= high) { return Err(Error::EmptyRange); } - let unsigned_max = ::core::$u_large::MAX; - let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned; - let ints_to_reject = if range > 0 { - let range = $u_large::from(range); - (unsigned_max - range + 1) % range + let range = high.wrapping_sub(low).wrapping_add(1) as $uty; + let thresh = if range > 0 { + let range = $sample_ty::from(range); + (range.wrapping_neg() % range) } else { 0 }; Ok(UniformInt { low, - // These are really $unsigned values, but store as $ty: - range: range as $ty, - z: ints_to_reject as $unsigned as $ty, + range: range as $ty, // type: $uty + thresh: thresh as $uty as $ty, // type: $sample_ty }) } + /// Sample from distribution, Lemire's method, unbiased #[inline] fn sample(&self, rng: &mut R) -> Self::X { - let range = self.range as $unsigned as $u_large; - if range > 0 { - let unsigned_max = ::core::$u_large::MAX; - let zone = unsigned_max - (self.z as $unsigned as $u_large); - loop { - let v: $u_large = rng.gen(); - let (hi, lo) = v.wmul(range); - if lo <= zone { - return self.low.wrapping_add(hi as $ty); - } - } - } else { - // Sample from the entire integer range. - rng.gen() + let range = self.range as $uty as $sample_ty; + if range == 0 { + return rng.gen(); } + + let thresh = self.thresh as $uty as $sample_ty; + let hi = loop { + let (hi, lo) = rng.gen::<$sample_ty>().wmul(range); + if lo >= thresh { + break hi; + } + }; + self.low.wrapping_add(hi as $ty) } #[inline] @@ -549,8 +546,15 @@ macro_rules! uniform_int_impl { Self::sample_single_inclusive(low, high - 1, rng) } + /// Sample single value, Canon's method, biased + /// + /// In the worst case, bias affects 1 in `2^n` samples where n is + /// 56 (`i8`), 48 (`i16`), 96 (`i32`), 64 (`i64`), 128 (`i128`). + #[cfg(not(feature = "unbiased"))] #[inline] - fn sample_single_inclusive(low_b: B1, high_b: B2, rng: &mut R) -> Result + fn sample_single_inclusive( + low_b: B1, high_b: B2, rng: &mut R, + ) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -560,33 +564,72 @@ macro_rules! uniform_int_impl { if !(low <= high) { return Err(Error::EmptyRange); } - let range = high.wrapping_sub(low).wrapping_add(1) as $unsigned as $u_large; - // If the above resulted in wrap-around to 0, the range is $ty::MIN..=$ty::MAX, - // and any integer will do. + let range = high.wrapping_sub(low).wrapping_add(1) as $uty as $sample_ty; if range == 0 { + // Range is MAX+1 (unrepresentable), so we need a special case return Ok(rng.gen()); } - let zone = if ::core::$unsigned::MAX <= ::core::u16::MAX as $unsigned { - // Using a modulus is faster than the approximation for - // i8 and i16. I suppose we trade the cost of one - // modulus for near-perfect branch prediction. - let unsigned_max: $u_large = ::core::$u_large::MAX; - let ints_to_reject = (unsigned_max - range + 1) % range; - unsigned_max - ints_to_reject - } else { - // conservative but fast approximation. `- 1` is necessary to allow the - // same comparison without bias. - (range << range.leading_zeros()).wrapping_sub(1) - }; + // generate a sample using a sensible integer type + let (mut result, lo_order) = rng.gen::<$sample_ty>().wmul(range); - loop { - let v: $u_large = rng.gen(); - let (hi, lo) = v.wmul(range); - if lo <= zone { - return Ok(low.wrapping_add(hi as $ty)); + // if the sample is biased... + if lo_order > range.wrapping_neg() { + // ...generate a new sample to reduce bias... + let (new_hi_order, _) = (rng.gen::<$sample_ty>()).wmul(range as $sample_ty); + // ... incrementing result on overflow + let is_overflow = lo_order.checked_add(new_hi_order as $sample_ty).is_none(); + result += is_overflow as $sample_ty; + } + + Ok(low.wrapping_add(result as $ty)) + } + + /// Sample single value, Canon's method, unbiased + #[cfg(feature = "unbiased")] + #[inline] + fn sample_single_inclusive( + low_b: B1, high_b: B2, rng: &mut R, + ) -> Result + where + B1: SampleBorrow<$ty> + Sized, + B2: SampleBorrow<$ty> + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low <= high) { + return Err(Error::EmptyRange); + } + let range = high.wrapping_sub(low).wrapping_add(1) as $uty as $sample_ty; + if range == 0 { + // Range is MAX+1 (unrepresentable), so we need a special case + return Ok(rng.gen()); + } + + let (mut result, mut lo) = rng.gen::<$sample_ty>().wmul(range); + + // In constrast to the biased sampler, we use a loop: + while lo > range.wrapping_neg() { + let (new_hi, new_lo) = (rng.gen::<$sample_ty>()).wmul(range); + match lo.checked_add(new_hi) { + Some(x) if x < $sample_ty::MAX => { + // Anything less than MAX: last term is 0 + break; + } + None => { + // Overflow: last term is 1 + result += 1; + break; + } + _ => { + // Unlikely case: must check next sample + lo = new_lo; + continue; + } } } + + Ok(low.wrapping_add(result as $ty)) } } }; @@ -668,22 +711,22 @@ macro_rules! uniform_simd_int_impl { // with bitwise OR let modulo = not_full_range.select(range, unsigned_max); // wrapping addition - let ints_to_reject = (unsigned_max - range + Simd::splat(1)) % modulo; + // TODO: replace with `range.wrapping_neg() % module` when Simd supports this. + let ints_to_reject = (Simd::splat(0) - range) % modulo; // When `range` is 0, `lo` of `v.wmul(range)` will always be // zero which means only one sample is needed. - let zone = unsigned_max - ints_to_reject; Ok(UniformInt { low, // These are really $unsigned values, but store as $ty: range: range.cast(), - z: zone.cast(), + thresh: ints_to_reject.cast(), }) } fn sample(&self, rng: &mut R) -> Self::X { let range: Simd<$unsigned, LANES> = self.range.cast(); - let zone: Simd<$unsigned, LANES> = self.z.cast(); + let thresh: Simd<$unsigned, LANES> = self.thresh.cast(); // This might seem very slow, generating a whole new // SIMD vector for every sample rejection. For most uses @@ -697,7 +740,7 @@ macro_rules! uniform_simd_int_impl { let mut v: Simd<$unsigned, LANES> = rng.gen(); loop { let (hi, lo) = v.wmul(range); - let mask = lo.simd_le(zone); + let mask = lo.simd_ge(thresh); if mask.all() { let hi: Simd<$ty, LANES> = hi.cast(); // wrapping addition @@ -740,7 +783,7 @@ impl SampleUniform for char { /// are used for surrogate pairs in UCS and UTF-16, and consequently are not /// valid Unicode code points. We must therefore avoid sampling values in this /// range. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UniformChar { sampler: UniformInt, @@ -1023,14 +1066,14 @@ uniform_float_impl! { f64x8, u64x8, f64, u64, 64 - 52 } /// /// Unless you are implementing [`UniformSampler`] for your own types, this type /// should not be used directly, use [`Uniform`] instead. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct UniformDuration { mode: UniformDurationMode, offset: u32, } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] enum UniformDurationMode { Small { @@ -1162,32 +1205,7 @@ mod tests { fn test_serialization_uniform_duration() { let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)).unwrap(); let de_distr: UniformDuration = bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); - assert_eq!( - distr.offset, de_distr.offset - ); - match (distr.mode, de_distr.mode) { - (UniformDurationMode::Small {secs: a_secs, nanos: a_nanos}, UniformDurationMode::Small {secs, nanos}) => { - assert_eq!(a_secs, secs); - - assert_eq!(a_nanos.0.low, nanos.0.low); - assert_eq!(a_nanos.0.range, nanos.0.range); - assert_eq!(a_nanos.0.z, nanos.0.z); - } - (UniformDurationMode::Medium {nanos: a_nanos} , UniformDurationMode::Medium {nanos}) => { - assert_eq!(a_nanos.0.low, nanos.0.low); - assert_eq!(a_nanos.0.range, nanos.0.range); - assert_eq!(a_nanos.0.z, nanos.0.z); - } - (UniformDurationMode::Large {max_secs:a_max_secs, max_nanos:a_max_nanos, secs:a_secs}, UniformDurationMode::Large {max_secs, max_nanos, secs} ) => { - assert_eq!(a_max_secs, max_secs); - assert_eq!(a_max_nanos, max_nanos); - - assert_eq!(a_secs.0.low, secs.0.low); - assert_eq!(a_secs.0.range, secs.0.range); - assert_eq!(a_secs.0.z, secs.0.z); - } - _ => panic!("`UniformDurationMode` was not serialized/deserialized correctly") - } + assert_eq!(distr, de_distr); } #[test] @@ -1195,16 +1213,11 @@ mod tests { fn test_uniform_serialization() { let unit_box: Uniform = Uniform::new(-1, 1).unwrap(); let de_unit_box: Uniform = bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); - - assert_eq!(unit_box.0.low, de_unit_box.0.low); - assert_eq!(unit_box.0.range, de_unit_box.0.range); - assert_eq!(unit_box.0.z, de_unit_box.0.z); + assert_eq!(unit_box.0, de_unit_box.0); let unit_box: Uniform = Uniform::new(-1., 1.).unwrap(); let de_unit_box: Uniform = bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); - - assert_eq!(unit_box.0.low, de_unit_box.0.low); - assert_eq!(unit_box.0.scale, de_unit_box.0.scale); + assert_eq!(unit_box.0, de_unit_box.0); } #[test] diff --git a/src/seq/index.rs b/src/seq/index.rs index 50523cc47c4..f29f72e1726 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -613,11 +613,11 @@ mod test { ); }; - do_test(10, 6, &[8, 3, 5, 9, 0, 6]); // floyd - do_test(25, 10, &[18, 14, 9, 15, 0, 13, 5, 24]); // floyd - do_test(300, 8, &[30, 283, 150, 1, 73, 13, 285, 35]); // floyd - do_test(300, 80, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace - do_test(300, 180, &[31, 289, 248, 154, 5, 78, 19, 286]); // inplace + do_test(10, 6, &[0, 9, 5, 4, 6, 8]); // floyd + do_test(25, 10, &[24, 20, 19, 9, 22, 16, 0, 14]); // floyd + do_test(300, 8, &[30, 283, 243, 150, 218, 240, 1, 189]); // floyd + do_test(300, 80, &[31, 289, 248, 154, 221, 243, 7, 192]); // inplace + do_test(300, 180, &[31, 289, 248, 154, 221, 243, 7, 192]); // inplace do_test(1_000_000, 8, &[ 103717, 963485, 826422, 509101, 736394, 807035, 5327, 632573, diff --git a/src/seq/mod.rs b/src/seq/mod.rs index d9b38e920d7..f4605b57750 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -762,7 +762,7 @@ mod test { let mut nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; assert_eq!(chars.choose(&mut r), Some(&'l')); - assert_eq!(nums.choose_mut(&mut r), Some(&mut 10)); + assert_eq!(nums.choose_mut(&mut r), Some(&mut 3)); #[cfg(feature = "alloc")] assert_eq!( @@ -770,13 +770,13 @@ mod test { .choose_multiple(&mut r, 8) .cloned() .collect::>(), - &['d', 'm', 'n', 'k', 'h', 'e', 'b', 'c'] + &['f', 'i', 'd', 'b', 'c', 'm', 'j', 'k'] ); #[cfg(feature = "alloc")] - assert_eq!(chars.choose_weighted(&mut r, |_| 1), Ok(&'f')); + assert_eq!(chars.choose_weighted(&mut r, |_| 1), Ok(&'l')); #[cfg(feature = "alloc")] - assert_eq!(nums.choose_weighted_mut(&mut r, |_| 1), Ok(&mut 5)); + assert_eq!(nums.choose_weighted_mut(&mut r, |_| 1), Ok(&mut 8)); let mut r = crate::test::rng(414); nums.shuffle(&mut r); @@ -1221,7 +1221,7 @@ mod test { chunk_remaining: 32, hint_total_size: false, }), - Some(39) + Some(91) ); assert_eq!( choose(ChunkHintedIterator { @@ -1230,7 +1230,7 @@ mod test { chunk_remaining: 32, hint_total_size: true, }), - Some(39) + Some(91) ); assert_eq!( choose(WindowHintedIterator { @@ -1238,7 +1238,7 @@ mod test { window_size: 32, hint_total_size: false, }), - Some(90) + Some(34) ); assert_eq!( choose(WindowHintedIterator { @@ -1246,7 +1246,7 @@ mod test { window_size: 32, hint_total_size: true, }), - Some(90) + Some(34) ); } @@ -1298,28 +1298,22 @@ mod test { #[test] fn value_stability_choose_multiple() { - fn do_test>(iter: I, v: &[u32]) { + fn do_test>(iter: I, v: &[u32]) { let mut rng = crate::test::rng(412); let mut buf = [0u32; 8]; - assert_eq!(iter.choose_multiple_fill(&mut rng, &mut buf), v.len()); + assert_eq!(iter.clone().choose_multiple_fill(&mut rng, &mut buf), v.len()); assert_eq!(&buf[0..v.len()], v); - } - do_test(0..4, &[0, 1, 2, 3]); - do_test(0..8, &[0, 1, 2, 3, 4, 5, 6, 7]); - do_test(0..100, &[58, 78, 80, 92, 43, 8, 96, 7]); - - #[cfg(feature = "alloc")] - { - fn do_test>(iter: I, v: &[u32]) { + #[cfg(feature = "alloc")] + { let mut rng = crate::test::rng(412); assert_eq!(iter.choose_multiple(&mut rng, v.len()), v); } - - do_test(0..4, &[0, 1, 2, 3]); - do_test(0..8, &[0, 1, 2, 3, 4, 5, 6, 7]); - do_test(0..100, &[58, 78, 80, 92, 43, 8, 96, 7]); } + + do_test(0..4, &[0, 1, 2, 3]); + do_test(0..8, &[0, 1, 2, 3, 4, 5, 6, 7]); + do_test(0..100, &[77, 95, 38, 23, 25, 8, 58, 40]); } #[test] From 7c97f9b86d144d9b0f41341aaeba38c5a3570150 Mon Sep 17 00:00:00 2001 From: warren Date: Sat, 25 Mar 2023 09:49:08 -0400 Subject: [PATCH 298/443] Merge master and fix conflicts. The main change is that Dirichlet now has a const trait N for the dimension. The other significant change is to propagate errors that could occur when Beta:new or Gamma::new is called. --- rand_distr/src/dirichlet.rs | 364 +++++++++++++++------------- rand_distr/tests/value_stability.rs | 16 +- 2 files changed, 206 insertions(+), 174 deletions(-) diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 882228eed16..c83334f8c8c 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -10,14 +10,16 @@ //! The dirichlet distribution. #![cfg(feature = "alloc")] use crate::{Beta, Distribution, Exp1, Gamma, Open01, StandardNormal}; -use alloc::{boxed::Box, vec, vec::Vec}; use core::fmt; use num_traits::{Float, NumCast}; use rand::Rng; +#[cfg(feature = "serde_with")] use serde_with::serde_as; + +use alloc::{boxed::Box, vec, vec::Vec}; #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -struct DirichletFromGamma +#[cfg_attr(feature = "serde_with", serde_as)] +struct DirichletFromGamma where F: Float, StandardNormal: Distribution, @@ -27,48 +29,57 @@ where samplers: Box<[Gamma]>, } -impl DirichletFromGamma +/// Error type returned from `DirchletFromGamma::new`. +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum DirichletFromGammaError { + /// Gamma::new(a, 1) failed. + GammmaNewFailed, +} + +impl DirichletFromGamma where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - // Construct a new `Dirichlet` with the given alpha parameter `alpha`. - // - // This function is part of a private implementation detail. - // It assumes that the input is correct, so no validation is done. + /// Construct a new `DirichletFromGamma` with the given parameters `alpha`. + /// + /// This function is part of a private implementation detail. + /// It assumes that the input is correct, so no validation of alpha is done. #[inline] - fn new(alpha: &[F]) -> DirichletFromGamma { - let gamma_dists = alpha - .iter() - .map(|a| Gamma::new(*a, F::one()).unwrap()) - .collect::>>() - .into_boxed_slice(); - DirichletFromGamma { - samplers: gamma_dists, + fn new(alpha: [F; N]) -> Result, DirichletFromGammaError> { + let mut gamma_dists = Vec::new(); + for a in alpha { + let dist = + Gamma::new(a, F::one()).map_err(|_| DirichletFromGammaError::GammmaNewFailed)?; + gamma_dists.push(dist); } + Ok(DirichletFromGamma { + samplers: gamma_dists.into_boxed_slice(), + }) } } -impl Distribution> for DirichletFromGamma +impl Distribution<[F; N]> for DirichletFromGamma where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - fn sample(&self, rng: &mut R) -> Vec { - let n = self.samplers.len(); - let mut samples = vec![F::zero(); n]; + fn sample(&self, rng: &mut R) -> [F; N] { + let mut samples = [F::zero(); N]; let mut sum = F::zero(); + for (s, g) in samples.iter_mut().zip(self.samplers.iter()) { *s = g.sample(rng); - sum = sum + (*s); + sum = sum + *s; } let invacc = F::one() / sum; for s in samples.iter_mut() { - *s = (*s) * invacc; + *s = *s * invacc; } samples } @@ -76,7 +87,7 @@ where #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -struct DirichletFromBeta +struct DirichletFromBeta where F: Float, StandardNormal: Distribution, @@ -86,7 +97,15 @@ where samplers: Box<[Beta]>, } -impl DirichletFromBeta +/// Error type returned from `DirchletFromBeta::new`. +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum DirichletFromBetaError { + /// Beta::new(a, b) failed. + BetaNewFailed, +} + +impl DirichletFromBeta where F: Float, StandardNormal: Distribution, @@ -97,45 +116,44 @@ where // // This function is part of a private implementation detail. // It assumes that the input is correct, so no validation is done. + // #[inline] - fn new(alpha: &[F]) -> DirichletFromBeta { - // Form the right-to-left cumulative sum of alpha, exluding the - // first element of alpha. E.g. if alpha = [a0, a1, a2, a3], then - // after the call to `alpha_sum_rl.reverse()` below, alpha_sum_rl - // will hold [a1+a2+a3, a2+a3, a3]. - let mut alpha_sum_rl: Vec = alpha - .iter() - .skip(1) - .rev() - // scan does the cumulative sum - .scan(F::zero(), |sum, x| { - *sum = *sum + *x; - Some(*sum) - }) - .collect(); - alpha_sum_rl.reverse(); - let beta_dists = alpha - .iter() - .zip(alpha_sum_rl.iter()) - .map(|t| Beta::new(*t.0, *t.1).unwrap()) - .collect::>>() - .into_boxed_slice(); - DirichletFromBeta { - samplers: beta_dists, + fn new(alpha: [F; N]) -> Result, DirichletFromBetaError> { + // `alpha_rev_csum` is the reverse of the cumulative sum of the + // reverse of `alpha[1..]`. E.g. if `alpha = [a0, a1, a2, a3]`, then + // `alpha_rev_csum` is `[a1 + a2 + a3, a2 + a3, a3]`. + // Note that instances of DirichletFromBeta will always have N >= 2, + // so the subtractions of 1, 2 and 3 from N in the following are safe. + let mut alpha_rev_csum = vec![alpha[N - 1]; N - 1]; + for k in 0..(N - 2) { + alpha_rev_csum[N - 3 - k] = alpha_rev_csum[N - 2 - k] + alpha[N - 2 - k]; } + + // Zip `alpha[..(N-1)]` and `alpha_rev_csum`; for the example + // `alpha = [a0, a1, a2, a3]`, the zip result holds the tuples + // `[(a0, a1+a2+a3), (a1, a2+a3), (a2, a3)]`. + // Then pass each tuple to `Beta::new()` to create the `Beta` + // instances. + let mut beta_dists = Vec::new(); + for (&a, &b) in alpha[..(N - 1)].iter().zip(alpha_rev_csum.iter()) { + let dist = Beta::new(a, b).map_err(|_| DirichletFromBetaError::BetaNewFailed)?; + beta_dists.push(dist); + } + Ok(DirichletFromBeta { + samplers: beta_dists.into_boxed_slice(), + }) } } -impl Distribution> for DirichletFromBeta +impl Distribution<[F; N]> for DirichletFromBeta where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - fn sample(&self, rng: &mut R) -> Vec { - let n = self.samplers.len(); - let mut samples = vec![F::zero(); n + 1]; + fn sample(&self, rng: &mut R) -> [F; N] { + let mut samples = [F::zero(); N]; let mut acc = F::one(); for (s, beta) in samples.iter_mut().zip(self.samplers.iter()) { @@ -143,25 +161,25 @@ where *s = acc * beta_sample; acc = acc * (F::one() - beta_sample); } - samples[n] = acc; + samples[N - 1] = acc; samples } } #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -enum DirichletRepr +#[cfg_attr(feature = "serde_with", serde_as)] +enum DirichletRepr where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - /// Dirichlet distribution that generates samples using the gamma distribution. - FromGamma(DirichletFromGamma), + /// Dirichlet distribution that generates samples using the Gamma distribution. + FromGamma(DirichletFromGamma), - /// Dirichlet distribution that generates samples using the beta distribution. - FromBeta(DirichletFromBeta), + /// Dirichlet distribution that generates samples using the Beta distribution. + FromBeta(DirichletFromBeta), } /// The Dirichlet distribution `Dirichlet(alpha)`. @@ -176,21 +194,21 @@ where /// use rand::prelude::*; /// use rand_distr::Dirichlet; /// -/// let dirichlet = Dirichlet::new(&[1.0, 2.0, 3.0]).unwrap(); +/// let dirichlet = Dirichlet::new([1.0, 2.0, 3.0]).unwrap(); /// let samples = dirichlet.sample(&mut rand::thread_rng()); /// println!("{:?} is from a Dirichlet([1.0, 2.0, 3.0]) distribution", samples); /// ``` #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +#[cfg_attr(feature = "serde_with", serde_as)] #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Dirichlet +pub struct Dirichlet where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - repr: DirichletRepr, + repr: DirichletRepr, } /// Error type returned from `Dirchlet::new`. @@ -201,6 +219,15 @@ pub enum Error { AlphaTooShort, /// `alpha <= 0.0` or `nan`. AlphaTooSmall, + /// `alpha` is subnormal. + /// Variate generation methods are not reliable with subnormal inputs. + AlphaSubnormal, + /// `alpha` is infinite. + AlphaInfinite, + /// Failed to create required Gamma distribution(s). + FailedToCreateGamma, + /// Failed to create required Beta distribition(s). + FailedToCreateBeta, /// `size < 2`. SizeTooSmall, } @@ -212,6 +239,14 @@ impl fmt::Display for Error { "less than 2 dimensions in Dirichlet distribution" } Error::AlphaTooSmall => "alpha is not positive in Dirichlet distribution", + Error::AlphaSubnormal => "alpha contains a subnormal value in Dirichlet distribution", + Error::AlphaInfinite => "alpha contains an infinite value in Dirichlet distribution", + Error::FailedToCreateGamma => { + "failed to create required Gamma distribution for Dirichlet distribution" + } + Error::FailedToCreateBeta => { + "failed to create required Beta distribition for Dirichlet distribution" + } }) } } @@ -220,7 +255,7 @@ impl fmt::Display for Error { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} -impl Dirichlet +impl Dirichlet where F: Float, StandardNormal: Distribution, @@ -229,53 +264,52 @@ where { /// Construct a new `Dirichlet` with the given alpha parameter `alpha`. /// - /// Requires `alpha.len() >= 2`, and each value in `alpha` must be positive. + /// Requires `alpha.len() >= 2`, and each value in `alpha` must be positive, + /// finite and not subnormal. #[inline] - pub fn new(alpha: &[F]) -> Result, Error> { - if alpha.len() < 2 { + pub fn new(alpha: [F; N]) -> Result, Error> { + if N < 2 { return Err(Error::AlphaTooShort); } for &ai in alpha.iter() { if !(ai > F::zero()) { + // This also catches nan. return Err(Error::AlphaTooSmall); } + if ai.is_infinite() { + return Err(Error::AlphaInfinite); + } + if !ai.is_normal() { + return Err(Error::AlphaSubnormal); + } } - if alpha.iter().all(|x| *x <= NumCast::from(0.1).unwrap()) { - // All the values in alpha are less than 0.1. + if alpha.iter().all(|&x| x <= NumCast::from(0.1).unwrap()) { + // Use the Beta method when all the alphas are less than 0.1 This + // threshold provides a reasonable compromise between using the faster + // Gamma method for as wide a range as possible while ensuring that + // the probability of generating nans is negligibly small. + let dist = DirichletFromBeta::new(alpha).map_err(|_| Error::FailedToCreateBeta)?; Ok(Dirichlet { - repr: DirichletRepr::FromBeta(DirichletFromBeta::new(alpha)), + repr: DirichletRepr::FromBeta(dist), }) } else { + let dist = DirichletFromGamma::new(alpha).map_err(|_| Error::FailedToCreateGamma)?; Ok(Dirichlet { - repr: DirichletRepr::FromGamma(DirichletFromGamma::new(alpha)), + repr: DirichletRepr::FromGamma(dist), }) } } - - /// Construct a new `Dirichlet` with the given shape parameter `alpha` and `size`. - /// - /// Requires `alpha > 0` and `size >= 2`. - #[inline] - pub fn new_with_size(alpha: F, size: usize) -> Result, Error> { - if !(alpha > F::zero()) { - return Err(Error::AlphaTooSmall); - } - if size < 2 { - return Err(Error::SizeTooSmall); - } - Ok(Dirichlet::new(vec![alpha; size].as_slice()).unwrap()) - } } -impl Distribution> for Dirichlet +impl Distribution<[F; N]> for Dirichlet where F: Float, StandardNormal: Distribution, Exp1: Distribution, Open01: Distribution, { - fn sample(&self, rng: &mut R) -> Vec { + fn sample(&self, rng: &mut R) -> [F; N] { match &self.repr { DirichletRepr::FromGamma(dirichlet) => dirichlet.sample(rng), DirichletRepr::FromBeta(dirichlet) => dirichlet.sample(rng), @@ -286,37 +320,11 @@ where #[cfg(test)] mod test { use super::*; - - // - // Check that the means of the components of n samples from - // the Dirichlet distribution agree with the expected means - // with a relative tolerance of rtol. - // - // This is a crude statistical test, but it will catch egregious - // mistakes. It will also also fail if any samples contain nan. - // - fn check_dirichlet_means(alpha: &Vec, n: i32, rtol: f64, seed: u64) { - let d = Dirichlet::new(&alpha).unwrap(); - let alpha_len = alpha.len(); - let mut rng = crate::test::rng(seed); - let mut sums = vec![0.0; alpha_len]; - for _ in 0..n { - let samples = d.sample(&mut rng); - for i in 0..alpha_len { - sums[i] += samples[i]; - } - } - let sample_mean: Vec = sums.iter().map(|x| x / n as f64).collect(); - let alpha_sum: f64 = alpha.iter().sum(); - let expected_mean: Vec = alpha.iter().map(|x| x / alpha_sum).collect(); - for i in 0..alpha_len { - assert_almost_eq!(sample_mean[i], expected_mean[i], rtol); - } - } + use alloc::vec::Vec; #[test] fn test_dirichlet() { - let d = Dirichlet::new(&[1.0, 2.0, 3.0]).unwrap(); + let d = Dirichlet::new([1.0, 2.0, 3.0]).unwrap(); let mut rng = crate::test::rng(221); let samples = d.sample(&mut rng); let _: Vec = samples @@ -329,36 +337,80 @@ mod test { } #[test] - fn test_dirichlet_with_param() { - let alpha = 0.5f64; - let size = 2; - let d = Dirichlet::new_with_size(alpha, size).unwrap(); - let mut rng = crate::test::rng(221); - let samples = d.sample(&mut rng); - let _: Vec = samples - .into_iter() - .map(|x| { - assert!(x > 0.0); - x - }) - .collect(); + #[should_panic] + fn test_dirichlet_invalid_length() { + Dirichlet::new([0.5]).unwrap(); + } + + #[test] + #[should_panic] + fn test_dirichlet_alpha_zero() { + Dirichlet::new([0.1, 0.0, 0.3]).unwrap(); + } + + #[test] + #[should_panic] + fn test_dirichlet_alpha_negative() { + Dirichlet::new([0.1, -1.5, 0.3]).unwrap(); + } + + #[test] + #[should_panic] + fn test_dirichlet_alpha_nan() { + Dirichlet::new([0.5, f64::NAN, 0.25]).unwrap(); + } + + #[test] + #[should_panic] + fn test_dirichlet_alpha_subnormal() { + Dirichlet::new([0.5, 1.5e-321, 0.25]).unwrap(); + } + + #[test] + #[should_panic] + fn test_dirichlet_alpha_inf() { + Dirichlet::new([0.5, f64::INFINITY, 0.25]).unwrap(); + } + + #[test] + fn dirichlet_distributions_can_be_compared() { + assert_eq!(Dirichlet::new([1.0, 2.0]), Dirichlet::new([1.0, 2.0])); + } + + /// Check that the means of the components of n samples from + /// the Dirichlet distribution agree with the expected means + /// with a relative tolerance of rtol. + /// + /// This is a crude statistical test, but it will catch egregious + /// mistakes. It will also also fail if any samples contain nan. + fn check_dirichlet_means(alpha: [f64; N], n: i32, rtol: f64, seed: u64) { + let d = Dirichlet::new(alpha).unwrap(); + let mut rng = crate::test::rng(seed); + let mut sums = [0.0; N]; + for _ in 0..n { + let samples = d.sample(&mut rng); + for i in 0..N { + sums[i] += samples[i]; + } + } + let sample_mean = sums.map(|x| x / n as f64); + let alpha_sum: f64 = alpha.iter().sum(); + let expected_mean = alpha.map(|x| x / alpha_sum); + for i in 0..N { + assert_almost_eq!(sample_mean[i], expected_mean[i], rtol); + } } #[test] fn test_dirichlet_means() { // Check the means of 20000 samples for several different alphas. - let alpha_set = vec![ - vec![0.5, 0.25], - vec![123.0, 75.0], - vec![2.0, 2.5, 5.0, 7.0], - vec![0.1, 8.0, 1.0, 2.0, 2.0, 0.85, 0.05, 12.5], - ]; let n = 20000; let rtol = 2e-2; let seed = 1317624576693539401; - for alpha in alpha_set { - check_dirichlet_means(&alpha, n, rtol, seed); - } + check_dirichlet_means([0.5, 0.25], n, rtol, seed); + check_dirichlet_means([123.0, 75.0], n, rtol, seed); + check_dirichlet_means([2.0, 2.5, 5.0, 7.0], n, rtol, seed); + check_dirichlet_means([0.1, 8.0, 1.0, 2.0, 2.0, 0.85, 0.05, 12.5], n, rtol, seed); } #[test] @@ -367,11 +419,11 @@ mod test { // components of 10000 samples are within 1% of the expected means. // With the sampling method based on gamma variates, this test would // fail, with about 10% of the samples containing nan. - let alpha = vec![0.001, 0.001, 0.001]; + let alpha = [0.001; 3]; let n = 10000; let rtol = 1e-2; let seed = 1317624576693539401; - check_dirichlet_means(&alpha, n, rtol, seed); + check_dirichlet_means(alpha, n, rtol, seed); } #[test] @@ -379,40 +431,10 @@ mod test { // With values of alpha that are all less than 0.1, check that the // means of the components of 150000 samples are within 0.1% of the // expected means. - let alpha = vec![0.05, 0.025, 0.075, 0.05]; + let alpha = [0.05, 0.025, 0.075, 0.05]; let n = 150000; let rtol = 1e-3; let seed = 1317624576693539401; - check_dirichlet_means(&alpha, n, rtol, seed); - } - - #[test] - #[should_panic] - fn test_dirichlet_invalid_length() { - Dirichlet::new_with_size(0.5f64, 1).unwrap(); - } - - #[test] - #[should_panic] - fn test_dirichlet_invalid_length_slice() { - Dirichlet::new(&[0.25]).unwrap(); - } - - #[test] - #[should_panic] - fn test_dirichlet_invalid_alpha() { - Dirichlet::new_with_size(0.0f64, 2).unwrap(); - } - - #[test] - #[should_panic] - fn test_dirichlet_invalid_alpha_slice() { - // 0 in alpha must result in a panic. - Dirichlet::new(&[0.1f64, 0.0f64, 1.5f64]).unwrap(); - } - - #[test] - fn dirichlet_distributions_can_be_compared() { - assert_eq!(Dirichlet::new(&[1.0, 2.0]), Dirichlet::new(&[1.0, 2.0])); + check_dirichlet_means(alpha, n, rtol, seed); } } diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index d3754705db5..88fe7d9ecab 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -348,16 +348,26 @@ fn weibull_stability() { fn dirichlet_stability() { let mut rng = get_rng(223); assert_eq!( - rng.sample(Dirichlet::new(&[1.0, 2.0, 3.0]).unwrap()), - vec![0.12941567177708177, 0.4702121891675036, 0.4003721390554146] + rng.sample(Dirichlet::new([1.0, 2.0, 3.0]).unwrap()), + [0.12941567177708177, 0.4702121891675036, 0.4003721390554146] ); - assert_eq!(rng.sample(Dirichlet::new_with_size(8.0, 5).unwrap()), vec![ + assert_eq!(rng.sample(Dirichlet::new([8.0; 5]).unwrap()), [ 0.17684200044809556, 0.29915953935953055, 0.1832858056608014, 0.1425623503573967, 0.19815030417417595 ]); + // Test stability for the case where all alphas are less than 0.1. + assert_eq!( + rng.sample(Dirichlet::new([0.05, 0.025, 0.075, 0.05]).unwrap()), + [ + 0.00027580456855692104, + 2.296135759821706e-20, + 3.004118281150937e-9, + 0.9997241924273248 + ] + ); } #[test] From 22256529a4756d41fef16b2d03a1183a29339035 Mon Sep 17 00:00:00 2001 From: warren Date: Sat, 25 Mar 2023 10:06:58 -0400 Subject: [PATCH 299/443] Copy-edit a comment and make it a doc comment. --- rand_distr/src/dirichlet.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index c83334f8c8c..244ff1897b5 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -112,11 +112,10 @@ where Exp1: Distribution, Open01: Distribution, { - // Construct a new `Dirichlet` with the given alpha parameter `alpha`. - // - // This function is part of a private implementation detail. - // It assumes that the input is correct, so no validation is done. - // + /// Construct a new `DirichletFromBeta` with the given parameters `alpha`. + /// + /// This function is part of a private implementation detail. + /// It assumes that the input is correct, so no validation of alpha is done. #[inline] fn new(alpha: [F; N]) -> Result, DirichletFromBetaError> { // `alpha_rev_csum` is the reverse of the cumulative sum of the From c6cca9cb81c72441c33b78fc74e3a5b33358334b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 26 Mar 2023 17:52:45 +0100 Subject: [PATCH 300/443] StepRng: better documentation of outputs for other types (#1304) --- src/rngs/mock.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/rngs/mock.rs b/src/rngs/mock.rs index a1745a490dd..0d9e0f905c9 100644 --- a/src/rngs/mock.rs +++ b/src/rngs/mock.rs @@ -13,12 +13,23 @@ use rand_core::{impls, Error, RngCore}; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; -/// A simple implementation of `RngCore` for testing purposes. +/// A mock generator yielding very predictable output /// /// This generates an arithmetic sequence (i.e. adds a constant each step) /// over a `u64` number, using wrapping arithmetic. If the increment is 0 /// the generator yields a constant. /// +/// Other integer types (64-bit and smaller) are produced via cast from `u64`. +/// +/// Other types are produced via their implementation of [`Rng`](crate::Rng) or +/// [`Distribution`](crate::distributions::Distribution). +/// Output values may not be intuitive and may change in future releases but +/// are considered +/// [portable](https://rust-random.github.io/book/portability.html). +/// (`bool` output is true when bit `1u64 << 31` is set.) +/// +/// # Example +/// /// ``` /// use rand::Rng; /// use rand::rngs::mock::StepRng; @@ -72,11 +83,12 @@ impl RngCore for StepRng { #[cfg(test)] mod tests { + #[cfg(any(feature = "alloc", feature = "serde1"))] + use super::StepRng; + #[test] #[cfg(feature = "serde1")] fn test_serialization_step_rng() { - use super::StepRng; - let some_rng = StepRng::new(42, 7); let de_some_rng: StepRng = bincode::deserialize(&bincode::serialize(&some_rng).unwrap()).unwrap(); @@ -84,4 +96,16 @@ mod tests { assert_eq!(some_rng.a, de_some_rng.a); } + + #[test] + #[cfg(feature = "alloc")] + fn test_bool() { + use crate::{Rng, distributions::Standard}; + + // If this result ever changes, update doc on StepRng! + let rng = StepRng::new(0, 1 << 31); + let result: alloc::vec::Vec = + rng.sample_iter(Standard).take(6).collect(); + assert_eq!(&result, &[false, true, false, true, false, true]); + } } From c12db0b8eeea36e0074834f09a36d110dd40627c Mon Sep 17 00:00:00 2001 From: warren Date: Thu, 30 Mar 2023 15:35:22 -0400 Subject: [PATCH 301/443] Use an array for DirichletFromGamma.samplers --- rand_distr/src/dirichlet.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 244ff1897b5..413c00476ab 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -26,7 +26,7 @@ where Exp1: Distribution, Open01: Distribution, { - samplers: Box<[Gamma]>, + samplers: [Gamma; N], } /// Error type returned from `DirchletFromGamma::new`. @@ -35,6 +35,9 @@ where enum DirichletFromGammaError { /// Gamma::new(a, 1) failed. GammmaNewFailed, + + /// gamma_dists.try_into() failed (in theory, this should not happen). + GammaArrayCreationFailed, } impl DirichletFromGamma @@ -57,7 +60,9 @@ where gamma_dists.push(dist); } Ok(DirichletFromGamma { - samplers: gamma_dists.into_boxed_slice(), + samplers: gamma_dists + .try_into() + .map_err(|_| DirichletFromGammaError::GammaArrayCreationFailed)?, }) } } From 7fcc5ebf7c72bc9698594d750cb58bb6cc812a3b Mon Sep 17 00:00:00 2001 From: Michael Schubart Date: Thu, 6 Apr 2023 18:10:58 +0100 Subject: [PATCH 302/443] Fix manual slice size calcuation (#1308) --- src/rng.rs | 2 +- src/rngs/adapter/reseeding.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rng.rs b/src/rng.rs index 1b53298d511..2f16740a1b6 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -350,7 +350,7 @@ macro_rules! impl_fill { rng.try_fill_bytes(unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut u8, - self.len() * mem::size_of::<$t>() + mem::size_of_val(self) ) })?; for x in self { diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index a47ab7c7484..fdcce0bdf4c 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -10,7 +10,7 @@ //! A wrapper around another PRNG that reseeds it after it //! generates a certain number of random bytes. -use core::mem::size_of; +use core::mem::size_of_val; use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; @@ -177,7 +177,7 @@ where // returning from a non-inlined function. return self.reseed_and_generate(results, global_fork_counter); } - let num_bytes = results.as_ref().len() * size_of::(); + let num_bytes = size_of_val(results.as_ref()); self.bytes_until_reseed -= num_bytes as i64; self.inner.generate(results); } @@ -247,7 +247,7 @@ where trace!("Reseeding RNG (periodic reseed)"); } - let num_bytes = results.as_ref().len() * size_of::<::Item>(); + let num_bytes = size_of_val(results.as_ref()); if let Err(e) = self.reseed() { warn!("Reseeding RNG failed: {}", e); From a747b1dcf7ff4a9194769c6bd3c060fc97d0557b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ihc=E7=AB=A5=E9=9E=8B=40=E6=8F=90=E4=B8=8D=E8=B5=B7?= =?UTF-8?q?=E5=8A=B2?= Date: Fri, 2 Jun 2023 22:32:25 +0800 Subject: [PATCH 303/443] fix feature typo (#1316) --- src/distributions/distribution.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index c6eaf5ef7ab..f305128f815 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -159,7 +159,7 @@ where { } -#[cfg(features = "nightly")] +#[cfg(feature = "nightly")] impl iter::TrustedLen for DistIter where D: Distribution, From 51d27a979585db733569c6ee5c954ee283f4ae4d Mon Sep 17 00:00:00 2001 From: LiosK Date: Sun, 4 Jun 2023 16:20:05 +0900 Subject: [PATCH 304/443] ReseedingRng: fix doc comment on reseeding behavior after UNIX fork (#1317) It reads "For ChaCha and Hc128 this is a maximum of fifteen `u32` values before reseeding" while rand_chacha v0.2.0 or higher actually consumes 63 `u32` values before reseeding. --- src/rngs/adapter/reseeding.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index fdcce0bdf4c..39ddfd71047 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -24,8 +24,8 @@ use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// - After `clone()`, the clone will be reseeded on first use. /// - When a process is forked on UNIX, the RNGs in both the parent and child /// processes will be reseeded just before the next call to -/// [`BlockRngCore::generate`], i.e. "soon". For ChaCha and Hc128 this is a -/// maximum of fifteen `u32` values before reseeding. +/// [`BlockRngCore::generate`], i.e. "soon". For ChaCha and Hc128, this is a +/// maximum of 63 and 15, respectively, `u32` values before reseeding. /// - After the PRNG has generated a configurable number of random bytes. /// /// # When should reseeding after a fixed number of generated bytes be used? From 0c4c6c02d818ec7ec6a9d7458297ca3993e4c2fe Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 12 Jun 2023 07:38:23 +0100 Subject: [PATCH 305/443] impl TrustedLen is unsafe (#1318) * impl TrustedLen is unsafe Implementing `TrustedLen` is now unsafe. From what I understand, quite a few people are still unhappy about the design since the `unsafe` marker in this case is a promise that `size_hint()` in a *different* impl is accurate, so this may change again. Anyway, we need to do something to fix our builds, and the alternative is just to remove this (I haven't a clue if it is enabling any important optimisations). * Remove TrustedLen impl for DistIter --- src/distributions/distribution.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index f305128f815..18ab30b8860 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -159,14 +159,6 @@ where { } -#[cfg(feature = "nightly")] -impl iter::TrustedLen for DistIter -where - D: Distribution, - R: Rng, -{ -} - /// A distribution of values of type `S` derived from the distribution `D` /// by mapping its output of type `T` through the closure `F`. /// From b593db692daed77e8ca6c7a7a7e8414633abc714 Mon Sep 17 00:00:00 2001 From: Warren Weckesser Date: Tue, 20 Jun 2023 10:50:19 -0400 Subject: [PATCH 306/443] Replace tab character with spaces in poisson.rs. (#1320) --- rand_distr/src/poisson.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 199313a28a1..50d74298356 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -69,7 +69,7 @@ where F: Float + FloatConst, Standard: Distribution /// Construct a new `Poisson` with the given shape parameter /// `lambda`. pub fn new(lambda: F) -> Result, Error> { - if !lambda.is_finite() { + if !lambda.is_finite() { return Err(Error::NonFinite); } if !(lambda > F::zero()) { From c7c65979a8d2709b2dbe221ba11ddd14c603abc3 Mon Sep 17 00:00:00 2001 From: Benjamin Lieser Date: Tue, 4 Jul 2023 17:42:15 +0200 Subject: [PATCH 307/443] Fix infinite loop in Binomial distribution --- rand_distr/src/binomial.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 0f49806aba1..54141ebe9bf 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -110,20 +110,34 @@ impl Distribution for Binomial { // Threshold for preferring the BINV algorithm. The paper suggests 10, // Ranlib uses 30, and GSL uses 14. const BINV_THRESHOLD: f64 = 10.; + + //Same value as in GSL + //It is possible for BINV to get stuck, so we break if x > BINV_MAX_X and try again + //It would be safer to set BINV_MAX_X to self.n, but it is extremly unlikely to be relevant + //When n*p < 10, so is n*p*q which is the variance, so a result > 110 would be 100 / sqrt(10) = 31 standard deviations away + const BINV_MAX_X : u64 = 110; if (self.n as f64) * p < BINV_THRESHOLD && self.n <= (core::i32::MAX as u64) { // Use the BINV algorithm. let s = p / q; let a = ((self.n + 1) as f64) * s; - let mut r = q.powi(self.n as i32); - let mut u: f64 = rng.gen(); - let mut x = 0; - while u > r as f64 { - u -= r; - x += 1; - r *= a / (x as f64) - s; + + result = 'outer: loop { + let mut r = q.powi(self.n as i32); + let mut u: f64 = rng.gen(); + let mut x = 0; + + while u > r as f64 { + u -= r; + x += 1; + if x > BINV_MAX_X { + continue 'outer; + } + r *= a / (x as f64) - s; + } + break x; + } - result = x; } else { // Use the BTPE algorithm. From dc8bc097e334af7cc1d2a69e6d69c11a960ad4eb Mon Sep 17 00:00:00 2001 From: Benjamin Lieser Date: Tue, 4 Jul 2023 23:04:40 +0200 Subject: [PATCH 308/443] Update rand_distr/src/binomial.rs Some formatting Co-authored-by: Vinzent Steinberg --- rand_distr/src/binomial.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 54141ebe9bf..da4063398d5 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -111,10 +111,10 @@ impl Distribution for Binomial { // Ranlib uses 30, and GSL uses 14. const BINV_THRESHOLD: f64 = 10.; - //Same value as in GSL - //It is possible for BINV to get stuck, so we break if x > BINV_MAX_X and try again - //It would be safer to set BINV_MAX_X to self.n, but it is extremly unlikely to be relevant - //When n*p < 10, so is n*p*q which is the variance, so a result > 110 would be 100 / sqrt(10) = 31 standard deviations away + // Same value as in GSL. + // It is possible for BINV to get stuck, so we break if x > BINV_MAX_X and try again. + // It would be safer to set BINV_MAX_X to self.n, but it is extremely unlikely to be relevant. + // When n*p < 10, so is n*p*q which is the variance, so a result > 110 would be 100 / sqrt(10) = 31 standard deviations away. const BINV_MAX_X : u64 = 110; if (self.n as f64) * p < BINV_THRESHOLD && self.n <= (core::i32::MAX as u64) { From c5af1f8072076c6d3778c70fe0d432e984ddc4c4 Mon Sep 17 00:00:00 2001 From: benjamin Date: Wed, 5 Jul 2023 08:56:46 +0200 Subject: [PATCH 309/443] Fix spaces --- rand_distr/src/binomial.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index da4063398d5..f01ec4913ea 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -123,19 +123,19 @@ impl Distribution for Binomial { let a = ((self.n + 1) as f64) * s; result = 'outer: loop { - let mut r = q.powi(self.n as i32); - let mut u: f64 = rng.gen(); - let mut x = 0; - - while u > r as f64 { - u -= r; - x += 1; - if x > BINV_MAX_X { - continue 'outer; - } - r *= a / (x as f64) - s; - } - break x; + let mut r = q.powi(self.n as i32); + let mut u: f64 = rng.gen(); + let mut x = 0; + + while u > r as f64 { + u -= r; + x += 1; + if x > BINV_MAX_X { + continue 'outer; + } + r *= a / (x as f64) - s; + } + break x; } } else { From a4739d8bd4c52c5e7643402641aa6a3ac1ac011c Mon Sep 17 00:00:00 2001 From: benjamin Date: Wed, 5 Jul 2023 08:58:12 +0200 Subject: [PATCH 310/443] Add test for infinite loop --- rand_distr/src/binomial.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index f01ec4913ea..4cd21fd8170 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -366,4 +366,15 @@ mod test { fn binomial_distributions_can_be_compared() { assert_eq!(Binomial::new(1, 1.0), Binomial::new(1, 1.0)); } + + #[test] + fn binomial_avoid_infinite_loop() { + let dist = Binomial::new(16000000, 3.1444753148558566e-10).unwrap(); + let mut sum: u64 = 0; + let mut rng = crate::test::rng(742); + for _ in 0..100_000 { + sum = sum.wrapping_add(dist.sample(&mut rng)); + } + assert_ne!(sum, 0); + } } From ee80b41619a92a5a1b5e93c85e6af3defd3dbfa2 Mon Sep 17 00:00:00 2001 From: aobatact Date: Fri, 14 Jul 2023 17:39:27 +0900 Subject: [PATCH 311/443] Add `DistString` impl to `Uniform` and `Slice` (#1315) * Add impl for `DistString` to `Uniform` and `Slice` * Fix `DistString` impl. --- src/distributions/slice.rs | 34 ++++++++++++++++++++++++++++++++++ src/distributions/uniform.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 398cad18b2c..224bf1712c1 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -7,6 +7,8 @@ // except according to those terms. use crate::distributions::{Distribution, Uniform}; +#[cfg(feature = "alloc")] +use alloc::string::String; /// A distribution to sample items uniformly from a slice. /// @@ -115,3 +117,35 @@ impl core::fmt::Display for EmptySlice { #[cfg(feature = "std")] impl std::error::Error for EmptySlice {} + +/// Note: the `String` is potentially left with excess capacity; optionally the +/// user may call `string.shrink_to_fit()` afterwards. +#[cfg(feature = "alloc")] +impl<'a> super::DistString for Slice<'a, char> { + fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { + // Get the max char length to minimize extra space. + // Limit this check to avoid searching for long slice. + let max_char_len = if self.slice.len() < 200 { + self.slice + .iter() + .try_fold(1, |max_len, char| { + // When the current max_len is 4, the result max_char_len will be 4. + Some(max_len.max(char.len_utf8())).filter(|len| *len < 4) + }) + .unwrap_or(4) + } else { + 4 + }; + + // Split the extension of string to reuse the unused capacities. + // Skip the split for small length or only ascii slice. + let mut extend_len = if max_char_len == 1 || len < 100 { len } else { len / 4 }; + let mut remain_len = len; + while extend_len > 0 { + string.reserve(max_char_len * extend_len); + string.extend(self.sample_iter(&mut *rng).take(extend_len)); + remain_len -= extend_len; + extend_len = extend_len.min(remain_len); + } + } +} diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index a2664768c9c..713961e8e0c 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -843,6 +843,24 @@ impl UniformSampler for UniformChar { } } +/// Note: the `String` is potentially left with excess capacity if the range +/// includes non ascii chars; optionally the user may call +/// `string.shrink_to_fit()` afterwards. +#[cfg(feature = "alloc")] +impl super::DistString for Uniform{ + fn append_string(&self, rng: &mut R, string: &mut alloc::string::String, len: usize) { + // Getting the hi value to assume the required length to reserve in string. + let mut hi = self.0.sampler.low + self.0.sampler.range - 1; + if hi >= CHAR_SURROGATE_START { + hi += CHAR_SURROGATE_LEN; + } + // Get the utf8 length of hi to minimize extra space. + let max_char_len = char::from_u32(hi).map(char::len_utf8).unwrap_or(4); + string.reserve(max_char_len * len); + string.extend(self.sample_iter(rng).take(len)) + } +} + /// The back-end implementing [`UniformSampler`] for floating-point types. /// /// Unless you are implementing [`UniformSampler`] for your own type, this type @@ -1376,6 +1394,22 @@ mod tests { let c = d.sample(&mut rng); assert!((c as u32) < 0xD800 || (c as u32) > 0xDFFF); } + #[cfg(feature = "alloc")] + { + use crate::distributions::DistString; + let string1 = d.sample_string(&mut rng, 100); + assert_eq!(string1.capacity(), 300); + let string2 = Uniform::new( + core::char::from_u32(0x0000).unwrap(), + core::char::from_u32(0x0080).unwrap(), + ).unwrap().sample_string(&mut rng, 100); + assert_eq!(string2.capacity(), 100); + let string3 = Uniform::new_inclusive( + core::char::from_u32(0x0000).unwrap(), + core::char::from_u32(0x0080).unwrap(), + ).unwrap().sample_string(&mut rng, 100); + assert_eq!(string3.capacity(), 200); + } } #[test] From 176e73647a8f662926f92057b5576c8bb36f01f6 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sat, 29 Jul 2023 09:12:57 +0200 Subject: [PATCH 312/443] Add `--generate-link-to-definition` option when building on docs.rs (#1327) * Add `--generate-link-to-definition` option when building on docs.rs * Fix documentation issues --- Cargo.toml | 4 ++-- rand_chacha/Cargo.toml | 3 +++ rand_core/Cargo.toml | 2 +- rand_distr/Cargo.toml | 3 +++ rand_distr/src/geometric.rs | 3 ++- rand_pcg/Cargo.toml | 3 +++ src/distributions/uniform.rs | 2 +- 7 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae26c0e25d0..8b783bb2aee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,9 @@ include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] # To build locally: -# RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open +# RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --generate-link-to-definition --open all-features = true -rustdoc-args = ["--cfg", "doc_cfg"] +rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"] [package.metadata.playground] features = ["small_rng", "serde1"] diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 7584b788265..da22317304b 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -15,6 +15,9 @@ categories = ["algorithms", "no-std"] edition = "2021" rust-version = "1.56" +[package.metadata.docs.rs] +rustdoc-args = ["--generate-link-to-definition"] + [dependencies] rand_core = { path = "../rand_core", version = "0.7.0" } ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index a3640068e00..7ad22fe7d9d 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -19,7 +19,7 @@ rust-version = "1.56" # To build locally: # RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open all-features = true -rustdoc-args = ["--cfg", "doc_cfg"] +rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"] [package.metadata.playground] all-features = true diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 49fd2e6f8a9..ec4db034a65 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -16,6 +16,9 @@ edition = "2021" rust-version = "1.56" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] +[package.metadata.docs.rs] +rustdoc-args = ["--generate-link-to-definition"] + [features] default = ["std"] std = ["alloc", "rand/std"] diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 3ea8b8f3e13..b8b396dd443 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -143,7 +143,8 @@ impl Distribution for Geometric /// /// See [`Geometric`](crate::Geometric) for the general geometric distribution. /// -/// Implemented via iterated [Rng::gen::().leading_zeros()]. +/// Implemented via iterated +/// [`Rng::gen::().leading_zeros()`](Rng::gen::().leading_zeros()). /// /// # Example /// ``` diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 80099769275..c31b461adf9 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -15,6 +15,9 @@ categories = ["algorithms", "no-std"] edition = "2021" rust-version = "1.56" +[package.metadata.docs.rs] +rustdoc-args = ["--generate-link-to-definition"] + [features] serde1 = ["serde"] diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 713961e8e0c..05bb1b60434 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -52,7 +52,7 @@ //! `low < high`). The example below merely wraps another back-end. //! //! The `new`, `new_inclusive` and `sample_single` functions use arguments of -//! type SampleBorrow to support passing in values by reference or +//! type `SampleBorrow` to support passing in values by reference or //! by value. In the implementation of these functions, you can choose to //! simply use the reference returned by [`SampleBorrow::borrow`], or you can choose //! to copy or clone the value, whatever is appropriate for your type. From 1b9d897cb729629a880a07e16184d38340de0219 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Wed, 9 Aug 2023 20:41:28 +0200 Subject: [PATCH 313/443] Add tests for `Uniform::try_from` with invalid ranges --- src/distributions/uniform.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 05bb1b60434..c86c4970bf3 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1678,6 +1678,14 @@ mod tests { assert_eq!(r.0.scale, 5.0); } + #[test] + fn test_uniform_from_std_range_bad_limits() { + assert!(Uniform::try_from(100..10).is_err()); + assert!(Uniform::try_from(100..100).is_err()); + assert!(Uniform::try_from(100.0..10.0).is_err()); + assert!(Uniform::try_from(100.0..100.0).is_err()); + } + #[test] fn test_uniform_from_std_range_inclusive() { let r = Uniform::try_from(2u32..=6).unwrap(); @@ -1689,6 +1697,14 @@ mod tests { assert!(r.0.scale < 5.0 + 1e-14); } + #[test] + fn test_uniform_from_std_range_inclusive_bad_limits() { + assert!(Uniform::try_from(100..=10).is_err()); + assert!(Uniform::try_from(100..=99).is_err()); + assert!(Uniform::try_from(100.0..=10.0).is_err()); + assert!(Uniform::try_from(100.0..=99.0).is_err()); + } + #[test] fn value_stability() { fn test_samples( From dcefa4aa6846869a1066abbc3e4b852a86e98183 Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Sun, 13 Aug 2023 18:06:24 +0300 Subject: [PATCH 314/443] Implement Standard support for signed NonZero* types --- src/distributions/integer.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index 418eea9ff13..3ef746b9928 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -18,8 +18,10 @@ use core::arch::x86::{__m128i, __m256i}; use core::arch::x86_64::__m512i; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::{__m128i, __m256i}; -use core::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, - NonZeroU128}; +use core::num::{ + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,NonZeroU128, + NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize,NonZeroI128 +}; #[cfg(feature = "simd_support")] use core::simd::*; use core::mem; @@ -114,6 +116,13 @@ impl_nzint!(NonZeroU64, NonZeroU64::new); impl_nzint!(NonZeroU128, NonZeroU128::new); impl_nzint!(NonZeroUsize, NonZeroUsize::new); +impl_nzint!(NonZeroI8, NonZeroI8::new); +impl_nzint!(NonZeroI16, NonZeroI16::new); +impl_nzint!(NonZeroI32, NonZeroI32::new); +impl_nzint!(NonZeroI64, NonZeroI64::new); +impl_nzint!(NonZeroI128, NonZeroI128::new); +impl_nzint!(NonZeroIsize, NonZeroIsize::new); + macro_rules! x86_intrinsic_impl { ($($intrinsic:ident),+) => {$( /// Available only on x86/64 platforms From f3dd0b885c4597b9617ca79987a0dd899ab29fcb Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Thu, 24 Aug 2023 03:07:56 -0700 Subject: [PATCH 315/443] fix partial_shuffle documentation (#1335) --- src/seq/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index f4605b57750..fc8504aa117 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -274,7 +274,7 @@ pub trait SliceRandom { /// /// If you only need to choose elements randomly and `amount > self.len()/2` /// then you may improve performance by taking - /// `amount = values.len() - amount` and using only the second slice. + /// `amount = self.len() - amount` and using only the second slice. /// /// If `amount` is greater than the number of elements in the slice, this /// will perform a full shuffle. From 7d510d73970254f62780fa08d8ed09052f9f9ea7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 10 Oct 2023 09:28:49 +0100 Subject: [PATCH 316/443] Improve doc of slice and iterator "choose" methods --- src/seq/mod.rs | 63 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index fc8504aa117..bbb46fc55fe 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -68,8 +68,10 @@ pub trait SliceRandom { /// The element type. type Item; - /// Returns a reference to one random element of the slice, or `None` if the - /// slice is empty. + /// Uniformly sample one element + /// + /// Returns a reference to one uniformly-sampled random element of + /// the slice, or `None` if the slice is empty. /// /// For slices, complexity is `O(1)`. /// @@ -88,14 +90,18 @@ pub trait SliceRandom { where R: Rng + ?Sized; - /// Returns a mutable reference to one random element of the slice, or - /// `None` if the slice is empty. + /// Uniformly sample one element (mut) + /// + /// Returns a mutable reference to one uniformly-sampled random element of + /// the slice, or `None` if the slice is empty. /// /// For slices, complexity is `O(1)`. fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> where R: Rng + ?Sized; + /// Uniformly sample `amount` distinct elements + /// /// Chooses `amount` elements from the slice at random, without repetition, /// and in random order. The returned iterator is appropriate both for /// collection into a `Vec` and filling an existing buffer (see example). @@ -126,8 +132,10 @@ pub trait SliceRandom { where R: Rng + ?Sized; - /// Similar to [`choose`], but where the likelihood of each outcome may be - /// specified. + /// Biased sampling for one element + /// + /// Returns a reference to one element of the slice, sampled according + /// to the provided weights. Returns `None` only if the slice is empty. /// /// The specified function `weight` maps each item `x` to a relative /// likelihood `weight(x)`. The probability of each item being selected is @@ -168,8 +176,10 @@ pub trait SliceRandom { + Clone + Default; - /// Similar to [`choose_mut`], but where the likelihood of each outcome may - /// be specified. + /// Biased sampling for one element (mut) + /// + /// Returns a mutable reference to one element of the slice, sampled according + /// to the provided weights. Returns `None` only if the slice is empty. /// /// The specified function `weight` maps each item `x` to a relative /// likelihood `weight(x)`. The probability of each item being selected is @@ -199,6 +209,8 @@ pub trait SliceRandom { + Clone + Default; + /// Biased sampling of `amount` distinct elements + /// /// Similar to [`choose_multiple`], but where the likelihood of each element's /// inclusion in the output may be specified. The elements are returned in an /// arbitrary, unspecified order. @@ -306,21 +318,28 @@ pub trait SliceRandom { /// I am 😀! /// ``` pub trait IteratorRandom: Iterator + Sized { - /// Choose one element at random from the iterator. + /// Uniformly sample one element /// - /// Returns `None` if and only if the iterator is empty. + /// Assuming that the [`Iterator::size_hint`] is correct, this method + /// returns one uniformly-sampled random element of the slice, or `None` + /// only if the slice is empty. Incorrect bounds on the `size_hint` may + /// cause this method to incorrectly return `None` if fewer elements than + /// the advertised `lower` bound are present and may prevent sampling of + /// elements beyond an advertised `upper` bound (i.e. incorrect `size_hint` + /// is memory-safe, but may result in unexpected `None` result and + /// non-uniform distribution). /// - /// This method uses [`Iterator::size_hint`] for optimisation. With an - /// accurate hint and where [`Iterator::nth`] is a constant-time operation - /// this method can offer `O(1)` performance. Where no size hint is + /// With an accurate [`Iterator::size_hint`] and where [`Iterator::nth`] is + /// a constant-time operation, this method can offer `O(1)` performance. + /// Where no size hint is /// available, complexity is `O(n)` where `n` is the iterator length. /// Partial hints (where `lower > 0`) also improve performance. /// - /// Note that the output values and the number of RNG samples used - /// depends on size hints. In particular, `Iterator` combinators that don't - /// change the values yielded but change the size hints may result in - /// `choose` returning different elements. If you want consistent results - /// and RNG usage consider using [`IteratorRandom::choose_stable`]. + /// Note further that [`Iterator::size_hint`] may affect the number of RNG + /// samples used as well as the result (while remaining uniform sampling). + /// Consider instead using [`IteratorRandom::choose_stable`] to avoid + /// [`Iterator`] combinators which only change size hints from affecting the + /// results. fn choose(mut self, rng: &mut R) -> Option where R: Rng + ?Sized, @@ -376,9 +395,7 @@ pub trait IteratorRandom: Iterator + Sized { } } - /// Choose one element at random from the iterator. - /// - /// Returns `None` if and only if the iterator is empty. + /// Uniformly sample one element (stable) /// /// This method is very similar to [`choose`] except that the result /// only depends on the length of the iterator and the values produced by @@ -437,6 +454,8 @@ pub trait IteratorRandom: Iterator + Sized { } } + /// Uniformly sample `amount` distinct elements into a buffer + /// /// Collects values at random from the iterator into a supplied buffer /// until that buffer is filled. /// @@ -476,7 +495,7 @@ pub trait IteratorRandom: Iterator + Sized { len } - /// Collects `amount` values at random from the iterator into a vector. + /// Uniformly sample `amount` distinct elements into a [`Vec`] /// /// This is equivalent to `choose_multiple_fill` except for the result type. /// From e609fa98fcdeb2757fdab73c48016265435690c4 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 10 Oct 2023 10:17:55 +0100 Subject: [PATCH 317/443] Replace MIPS with PPC32 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14639f24d38..bef0e3d513e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -113,7 +113,7 @@ jobs: matrix: include: - os: ubuntu-latest - target: mips-unknown-linux-gnu + target: powerpc-unknown-linux-gnu toolchain: stable steps: From e42c385872e373a9bd0cf8c4992ac6ab54a8557a Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 11 Oct 2023 09:54:54 +0200 Subject: [PATCH 318/443] MSRV=1.60, use more permissive cfg on test * Use cfg(panic = unwind) instead of a check for wasm32 to see if unwinding is supported This allows the test to run on a future wasm target with exception support as well as running the test suite with panic=abort on non-wasm targets. * Bump MSRV to 1.60 --- .github/workflows/test.yml | 2 +- Cargo.toml | 2 +- README.md | 4 ++-- rand_chacha/Cargo.toml | 2 +- rand_chacha/README.md | 2 +- rand_core/Cargo.toml | 2 +- rand_core/README.md | 2 +- rand_distr/Cargo.toml | 2 +- rand_distr/README.md | 2 +- rand_distr/benches/Cargo.toml | 2 +- rand_pcg/Cargo.toml | 2 +- rand_pcg/README.md | 2 +- src/distributions/uniform.rs | 6 +----- 13 files changed, 14 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bef0e3d513e..4e20b0675c1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,7 +47,7 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu variant: MSRV - toolchain: 1.56.0 + toolchain: 1.60.0 - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu diff --git a/Cargo.toml b/Cargo.toml index 8b783bb2aee..98dbdeb1dca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["random", "rng"] categories = ["algorithms", "no-std"] autobenches = true edition = "2021" -rust-version = "1.56" +rust-version = "1.60" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] diff --git a/README.md b/README.md index c4704f3dc44..30c697922bf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A Rust library for random number generation, featuring: @@ -97,7 +97,7 @@ issue tracker with the keyword `yank` *should* uncover the motivation. ### Rust version requirements -The Minimum Supported Rust Version (MSRV) is `rustc >= 1.56.0`. +The Minimum Supported Rust Version (MSRV) is `rustc >= 1.60.0`. Older releases may work (depending on feature configuration) but are untested. ## Crate Features diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index da22317304b..847cfdde686 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -13,7 +13,7 @@ ChaCha random number generator keywords = ["random", "rng", "chacha"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.56" +rust-version = "1.60" [package.metadata.docs.rs] rustdoc-args = ["--generate-link-to-definition"] diff --git a/rand_chacha/README.md b/rand_chacha/README.md index 851490e22aa..0fd1b64c0d4 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) [![API](https://docs.rs/rand_chacha/badge.svg)](https://docs.rs/rand_chacha) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A cryptographically secure random number generator that uses the ChaCha algorithm. diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 7ad22fe7d9d..6dd6f843a1d 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -13,7 +13,7 @@ Core random number generator traits and tools for implementation. keywords = ["random", "rng"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.56" +rust-version = "1.60" [package.metadata.docs.rs] # To build locally: diff --git a/rand_core/README.md b/rand_core/README.md index 4174ff19484..a08f7c99251 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_core) [![API](https://docs.rs/rand_core/badge.svg)](https://docs.rs/rand_core) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Core traits and error types of the [rand] library, plus tools for implementing RNGs. diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index ec4db034a65..a5907eb4fdb 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -13,7 +13,7 @@ Sampling from random number distributions keywords = ["random", "rng", "distribution", "probability"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.56" +rust-version = "1.60" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] diff --git a/rand_distr/README.md b/rand_distr/README.md index d11a3744c41..016e8981d85 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a full suite of random number distribution sampling routines. diff --git a/rand_distr/benches/Cargo.toml b/rand_distr/benches/Cargo.toml index aeba667b3b4..2dd82c7973a 100644 --- a/rand_distr/benches/Cargo.toml +++ b/rand_distr/benches/Cargo.toml @@ -5,7 +5,7 @@ authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" description = "Criterion benchmarks of the rand_distr crate" edition = "2021" -rust-version = "1.56" +rust-version = "1.60" publish = false [workspace] diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index c31b461adf9..1d4e811a869 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -13,7 +13,7 @@ Selected PCG random number generators keywords = ["random", "rng", "pcg"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.56" +rust-version = "1.60" [package.metadata.docs.rs] rustdoc-args = ["--generate-link-to-definition"] diff --git a/rand_pcg/README.md b/rand_pcg/README.md index ce6d1f37c6c..da1a1beeffb 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_pcg) [![API](https://docs.rs/rand_pcg/badge.svg)](https://docs.rs/rand_pcg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a selection of PCG random number generators. diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index c86c4970bf3..ccf7b6e8ac9 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1536,11 +1536,7 @@ mod tests { } #[test] - #[cfg(all( - feature = "std", - not(target_arch = "wasm32"), - not(target_arch = "asmjs") - ))] + #[cfg(all(feature = "std", panic = "unwind"))] fn test_float_assertions() { use super::SampleUniform; use std::panic::catch_unwind; From 9a02c819cc1e4ec6959ae25eafbb5cf6acb68234 Mon Sep 17 00:00:00 2001 From: Nathan West Date: Wed, 11 Oct 2023 03:56:15 -0400 Subject: [PATCH 319/443] Simplify macro implementation for tuples (#1340) --- src/distributions/other.rs | 65 +++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 9ddce76366e..a8e1bbd7963 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -191,49 +191,50 @@ where } } +/// Implement `Distribution<(A, B, C, ...)> for Standard, using the list of +/// identifiers macro_rules! tuple_impl { - // use variables to indicate the arity of the tuple - ($($tyvar:ident),* ) => { - // the trailing commas are for the 1 tuple - impl< $( $tyvar ),* > - Distribution<( $( $tyvar ),* , )> - for Standard - where $( Standard: Distribution<$tyvar> ),* + ($($tyvar:ident)*) => { + impl< $($tyvar,)* > Distribution<($($tyvar,)*)> for Standard + where $( + Standard: Distribution< $tyvar >, + )* { #[inline] - fn sample(&self, _rng: &mut R) -> ( $( $tyvar ),* , ) { - ( + fn sample(&self, rng: &mut R) -> ( $($tyvar,)* ) { + let out = ($( // use the $tyvar's to get the appropriate number of // repeats (they're not actually needed) - $( - _rng.gen::<$tyvar>() - ),* - , - ) + rng.gen::<$tyvar>() + ,)*); + + // Suppress the unused variable warning for empty tuple + let _rng = rng; + + out } } } } -impl Distribution<()> for Standard { - #[allow(clippy::unused_unit)] - #[inline] - fn sample(&self, _: &mut R) -> () { - () - } +/// Looping wrapper for `tuple_impl`. Given (A, B, C), it also generates +/// implementations for (A, B) and (A,) +macro_rules! tuple_impls { + ($($tyvar:ident)*) => {tuple_impls!{[] $($tyvar)*}}; + + ([$($prefix:ident)*] $head:ident $($tail:ident)*) => { + tuple_impl!{$($prefix)*} + tuple_impls!{[$($prefix)* $head] $($tail)*} + }; + + + ([$($prefix:ident)*]) => { + tuple_impl!{$($prefix)*} + }; + } -tuple_impl! {A} -tuple_impl! {A, B} -tuple_impl! {A, B, C} -tuple_impl! {A, B, C, D} -tuple_impl! {A, B, C, D, E} -tuple_impl! {A, B, C, D, E, F} -tuple_impl! {A, B, C, D, E, F, G} -tuple_impl! {A, B, C, D, E, F, G, H} -tuple_impl! {A, B, C, D, E, F, G, H, I} -tuple_impl! {A, B, C, D, E, F, G, H, I, J} -tuple_impl! {A, B, C, D, E, F, G, H, I, J, K} -tuple_impl! {A, B, C, D, E, F, G, H, I, J, K, L} + +tuple_impls! {A B C D E F G H I J K L} impl Distribution<[T; N]> for Standard where Standard: Distribution From 5216f9a7fc7a541101fcc8b3de77c48d2118b08c Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Sun, 29 Oct 2023 15:23:48 -0400 Subject: [PATCH 320/443] Remove some unsafe code --- Cargo.toml | 3 ++- rand_core/Cargo.toml | 1 + rand_core/src/impls.rs | 18 +++--------------- src/distributions/integer.rs | 3 +-- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98dbdeb1dca..21781ef5385 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ alloc = ["rand_core/alloc"] getrandom = ["rand_core/getrandom"] # Option (requires nightly Rust): experimental SIMD support -simd_support = [] +simd_support = ["zerocopy/simd-nightly"] # Option (enabled by default): enable StdRng std_rng = ["rand_chacha"] @@ -69,6 +69,7 @@ rand_core = { path = "rand_core", version = "0.7.0" } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "0.4.0", default-features = false, optional = true } +zerocopy = { version = "0.7.20", default-features = false, features = ["simd"] } [target.'cfg(unix)'.dependencies] # Used for fork protection (reseeding.rs) diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 6dd6f843a1d..37c2a563a35 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -32,3 +32,4 @@ serde1 = ["serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } getrandom = { version = "0.2", optional = true } +zerocopy = { version = "0.7.20", default-features = false } diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 8f99ef813a5..d7dcd3457d3 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -19,6 +19,7 @@ use crate::RngCore; use core::cmp::min; +use zerocopy::AsBytes; /// Implement `next_u64` via `next_u32`, little-endian order. pub fn next_u64_via_u32(rng: &mut R) -> u64 { @@ -52,31 +53,18 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } } -trait Observable: Copy { +trait Observable: AsBytes + Copy { fn to_le(self) -> Self; - - // Contract: observing self is memory-safe (implies no uninitialised padding) - fn as_byte_slice(x: &[Self]) -> &[u8]; } impl Observable for u32 { fn to_le(self) -> Self { self.to_le() } - fn as_byte_slice(x: &[Self]) -> &[u8] { - let ptr = x.as_ptr() as *const u8; - let len = x.len() * core::mem::size_of::(); - unsafe { core::slice::from_raw_parts(ptr, len) } - } } impl Observable for u64 { fn to_le(self) -> Self { self.to_le() } - fn as_byte_slice(x: &[Self]) -> &[u8] { - let ptr = x.as_ptr() as *const u8; - let len = x.len() * core::mem::size_of::(); - unsafe { core::slice::from_raw_parts(ptr, len) } - } } /// Fill dest from src @@ -98,7 +86,7 @@ fn fill_via_chunks(src: &mut [T], dest: &mut [u8]) -> (usize, usi } } - dest[..byte_len].copy_from_slice(&T::as_byte_slice(&src[..num_chunks])[..byte_len]); + dest[..byte_len].copy_from_slice(&<[T]>::as_bytes(&src[..num_chunks])[..byte_len]); (num_chunks, byte_len) } diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index 3ef746b9928..e5d320b2550 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -134,8 +134,7 @@ macro_rules! x86_intrinsic_impl { let mut buf = [0_u8; mem::size_of::<$intrinsic>()]; rng.fill_bytes(&mut buf); // x86 is little endian so no need for conversion - // SAFETY: we know [u8; N] and $intrinsic have the same size - unsafe { mem::transmute_copy(&buf) } + zerocopy::transmute!(buf) } } )+}; From d0499f0a26c6167130e91d0c1e7a0815c5df75a4 Mon Sep 17 00:00:00 2001 From: OldEnglishSheepdog <149329016+OldEnglishSheepdog@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:39:38 +0100 Subject: [PATCH 321/443] Correcting method name in comment (#1350) Co-authored-by: Detlef --- rand_distr/src/pert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index db89fff7bfb..9ed79bf28ff 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -78,7 +78,7 @@ where { /// Set up the PERT distribution with defined `min`, `max` and `mode`. /// - /// This is equivalent to calling `Pert::new_shape` with `shape == 4.0`. + /// This is equivalent to calling `Pert::new_with_shape` with `shape == 4.0`. #[inline] pub fn new(min: F, max: F, mode: F) -> Result, PertError> { Pert::new_with_shape(min, max, mode, F::from(4.).unwrap()) From d9a89c8f13be1db2229a7a02b4a940a121b5a1b8 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 30 Oct 2023 21:24:34 +0100 Subject: [PATCH 322/443] Add example for initializing a PCG RNG (#1347) --- rand_pcg/src/lib.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index 341313954e7..c4228edfc6c 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Developers of the Rand project. +// Copyright 2018-2023 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -27,6 +27,18 @@ //! Both of these use 16 bytes of state and 128-bit seeds, and are considered //! value-stable (i.e. any change affecting the output given a fixed seed would //! be considered a breaking change to the crate). +//! +//! # Example +//! +//! To initialize a generator, use the [`SeedableRng`][rand_core::SeedableRng] trait: +//! +//! ```rust +//! use rand::{SeedableRng, Rng}; +//! use rand_pcg::Pcg64Mcg; +//! +//! let mut rng = Pcg64Mcg::from_entropy(); +//! let x: u32 = rng.gen(); +//! ``` #![doc( html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", From 240cd439fcafb79e36d83c40caa0ba5c5d99a3f7 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Tue, 31 Oct 2023 09:53:02 +0100 Subject: [PATCH 323/443] Mention that `gen_range` may overflow for floats (#1337) (#1351) --- src/rng.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rng.rs b/src/rng.rs index a66b43e4c99..206275f8d76 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -103,7 +103,7 @@ pub trait Rng: RngCore { /// /// # Panics /// - /// Panics if the range is empty. + /// Panics if the range is empty, or if `high - low` overflows for floats. /// /// # Example /// From ef89cbefaf484270dc3936d5d32fc2a73314173c Mon Sep 17 00:00:00 2001 From: Alex Saveau Date: Mon, 6 Nov 2023 00:15:07 -0800 Subject: [PATCH 324/443] Support using std without getrandom or rand_chacha (#1354) Support using std without getrandom or rand_chacha Signed-off-by: Alex Saveau --- Cargo.toml | 6 +++--- rand_core/Cargo.toml | 2 +- rand_core/src/error.rs | 8 +++----- rand_distr/Cargo.toml | 2 +- src/lib.rs | 10 +++++----- src/prelude.rs | 4 ++-- src/rngs/mod.rs | 4 ++-- src/rngs/std.rs | 1 + src/rngs/thread.rs | 4 ++-- 9 files changed, 20 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 21781ef5385..da14228a547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,13 +28,13 @@ features = ["small_rng", "serde1"] [features] # Meta-features: -default = ["std", "std_rng"] +default = ["std", "std_rng", "getrandom"] nightly = [] # some additions requiring nightly Rust serde1 = ["serde", "rand_core/serde1"] # Option (enabled by default): without "std" rand uses libcore; this option # enables functionality expected to be available on a standard platform. -std = ["rand_core/std", "rand_chacha/std", "alloc", "getrandom", "libc"] +std = ["rand_core/std", "rand_chacha?/std", "alloc", "libc"] # Option: "alloc" enables support for Vec and Box when not using "std" alloc = ["rand_core/alloc"] @@ -65,7 +65,7 @@ members = [ ] [dependencies] -rand_core = { path = "rand_core", version = "0.7.0" } +rand_core = { path = "rand_core", version = "0.7.0", default-features = false } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "0.4.0", default-features = false, optional = true } diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 37c2a563a35..8c9d902a70c 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -25,7 +25,7 @@ rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"] all-features = true [features] -std = ["alloc", "getrandom", "getrandom/std"] # use std library; should be default but for above bug +std = ["alloc", "getrandom?/std"] alloc = [] # enables Vec and Box support without std serde1 = ["serde"] # enables serde for BlockRng wrapper diff --git a/rand_core/src/error.rs b/rand_core/src/error.rs index 411896f2c47..1a5092fe82b 100644 --- a/rand_core/src/error.rs +++ b/rand_core/src/error.rs @@ -50,9 +50,7 @@ impl Error { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] #[inline] pub fn new(err: E) -> Self - where - E: Into>, - { + where E: Into> { Error { inner: err.into() } } @@ -125,7 +123,7 @@ impl fmt::Debug for Error { { getrandom::Error::from(self.code).fmt(f) } - #[cfg(not(feature = "getrandom"))] + #[cfg(not(any(feature = "getrandom", feature = "std")))] { write!(f, "Error {{ code: {} }}", self.code) } @@ -142,7 +140,7 @@ impl fmt::Display for Error { { getrandom::Error::from(self.code).fmt(f) } - #[cfg(not(feature = "getrandom"))] + #[cfg(not(any(feature = "getrandom", feature = "std")))] { write!(f, "error code {}", self.code) } diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index a5907eb4fdb..9d3baee0a88 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -35,7 +35,7 @@ serde_with = { version = "1.14.0", optional = true } [dev-dependencies] rand_pcg = { version = "0.4.0", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "0.9.0", default-features = false, features = ["std_rng", "std", "small_rng"] } +rand = { path = "..", version = "0.9.0", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.13", features = [ "std" ] } # Special functions for testing distributions diff --git a/src/lib.rs b/src/lib.rs index 755b5ba6e9c..69b6293b8bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,11 +101,11 @@ pub mod rngs; pub mod seq; // Public exports -#[cfg(all(feature = "std", feature = "std_rng"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use crate::rngs::thread::thread_rng; pub use rng::{Fill, Rng}; -#[cfg(all(feature = "std", feature = "std_rng"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] use crate::distributions::{Distribution, Standard}; /// Generates a random value using the thread-local random number generator. @@ -152,8 +152,8 @@ use crate::distributions::{Distribution, Standard}; /// /// [`Standard`]: distributions::Standard /// [`ThreadRng`]: rngs::ThreadRng -#[cfg(all(feature = "std", feature = "std_rng"))] -#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))))] #[inline] pub fn random() -> T where Standard: Distribution { @@ -173,7 +173,7 @@ mod test { } #[test] - #[cfg(all(feature = "std", feature = "std_rng"))] + #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] fn test_random() { let _n: usize = random(); let _f: f32 = random(); diff --git a/src/prelude.rs b/src/prelude.rs index 51c457e3f9e..1ce747b6255 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -25,10 +25,10 @@ pub use crate::rngs::SmallRng; #[cfg(feature = "std_rng")] #[doc(no_inline)] pub use crate::rngs::StdRng; #[doc(no_inline)] -#[cfg(all(feature = "std", feature = "std_rng"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use crate::rngs::ThreadRng; #[doc(no_inline)] pub use crate::seq::{IteratorRandom, SliceRandom}; #[doc(no_inline)] -#[cfg(all(feature = "std", feature = "std_rng"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use crate::{random, thread_rng}; #[doc(no_inline)] pub use crate::{CryptoRng, Rng, RngCore, SeedableRng}; diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index ac3c2c595da..9013c57d10c 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -109,11 +109,11 @@ mod xoshiro128plusplus; #[cfg(feature = "small_rng")] mod small; #[cfg(feature = "std_rng")] mod std; -#[cfg(all(feature = "std", feature = "std_rng"))] pub(crate) mod thread; +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub(crate) mod thread; #[cfg(feature = "small_rng")] pub use self::small::SmallRng; #[cfg(feature = "std_rng")] pub use self::std::StdRng; -#[cfg(all(feature = "std", feature = "std_rng"))] pub use self::thread::ThreadRng; +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use self::thread::ThreadRng; #[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] #[cfg(feature = "getrandom")] pub use rand_core::OsRng; diff --git a/src/rngs/std.rs b/src/rngs/std.rs index cdae8fab01c..ddc3014dfe1 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -10,6 +10,7 @@ use crate::{CryptoRng, Error, RngCore, SeedableRng}; +#[cfg(feature = "getrandom")] pub(crate) use rand_chacha::ChaCha12Core as Core; use rand_chacha::ChaCha12Rng as Rng; diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 78cecde5755..6c8d83c02eb 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -63,7 +63,7 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// /// [`ReseedingRng`]: crate::rngs::adapter::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng -#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] +#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))))] #[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync @@ -107,7 +107,7 @@ thread_local!( /// println!("A simulated die roll: {}", rng.gen_range(1..=6)); /// # } /// ``` -#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))] +#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))))] pub fn thread_rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng } From 1924e120cc77aac353adaae9c3a012ce2218ab8e Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 27 Nov 2023 21:52:23 +0100 Subject: [PATCH 325/443] Fix doc test to work without `rand` and `from_entropy` --- rand_pcg/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index c4228edfc6c..86484f7633b 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -33,11 +33,12 @@ //! To initialize a generator, use the [`SeedableRng`][rand_core::SeedableRng] trait: //! //! ```rust -//! use rand::{SeedableRng, Rng}; +//! use rand_core::{SeedableRng, RngCore}; //! use rand_pcg::Pcg64Mcg; //! -//! let mut rng = Pcg64Mcg::from_entropy(); -//! let x: u32 = rng.gen(); +//! let mut rng = Pcg64Mcg::seed_from_u64(0); +//! // Alternatively, you may use `Pcg64Mcg::from_entropy()`. +//! let x: u32 = rng.next_u32(); //! ``` #![doc( From 1cc3f881a1368cbe936ae3d8627ee316b237ce3e Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 27 Nov 2023 21:54:34 +0100 Subject: [PATCH 326/443] Update changelog --- rand_pcg/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index 8bc112adabd..63a37781707 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Add `Lcg128CmDxsm64` generator compatible with NumPy's `PCG64DXSM` (#1202) +- Add examples for initializing the RNGs ## [0.3.1] - 2021-06-15 - Add `advance` methods to RNGs (#1111) From c427cff1d5a99066b4627e5d438400dd4ffacc5f Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 14 Dec 2023 08:50:57 +0100 Subject: [PATCH 327/443] Fix clippy warnings --- benches/seq_choose.rs | 2 +- benches/shuffle.rs | 2 +- rand_core/src/block.rs | 36 +++++++++++------------ rand_distr/src/binomial.rs | 4 +-- rand_distr/src/cauchy.rs | 2 +- rand_distr/src/geometric.rs | 6 ++-- rand_distr/src/hypergeometric.rs | 6 ++-- rand_distr/src/normal_inverse_gaussian.rs | 2 +- rand_distr/src/skew_normal.rs | 2 +- src/distributions/bernoulli.rs | 2 +- src/distributions/float.rs | 2 +- src/distributions/other.rs | 2 +- src/distributions/uniform.rs | 4 ++- src/lib.rs | 1 + src/seq/coin_flipper.rs | 4 +-- src/seq/index.rs | 8 ++--- 16 files changed, 44 insertions(+), 41 deletions(-) diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 2c34d77ced7..ccf7e5825aa 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -23,7 +23,7 @@ pub fn bench(c: &mut Criterion) { } fn bench_rng(c: &mut Criterion, rng_name: &'static str) { - for length in [1, 2, 3, 10, 100, 1000].map(|x| black_box(x)) { + for length in [1, 2, 3, 10, 100, 1000].map(black_box) { c.bench_function( format!("choose_size-hinted_from_{length}_{rng_name}").as_str(), |b| { diff --git a/benches/shuffle.rs b/benches/shuffle.rs index 3d6878219f7..4d6e31fa38c 100644 --- a/benches/shuffle.rs +++ b/benches/shuffle.rs @@ -23,7 +23,7 @@ pub fn bench(c: &mut Criterion) { } fn bench_rng(c: &mut Criterion, rng_name: &'static str) { - for length in [1, 2, 3, 10, 100, 1000, 10000].map(|x| black_box(x)) { + for length in [1, 2, 3, 10, 100, 1000, 10000].map(black_box) { c.bench_function(format!("shuffle_{length}_{rng_name}").as_str(), |b| { let mut rng = Rng::seed_from_u64(123); let mut vec: Vec = (0..length).collect(); diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 5ad459d0fb4..387e7aa3958 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -470,20 +470,20 @@ mod test { let mut rng3 = rng1.clone(); let mut a = [0; 16]; - (&mut a[..4]).copy_from_slice(&rng1.next_u32().to_le_bytes()); - (&mut a[4..12]).copy_from_slice(&rng1.next_u64().to_le_bytes()); - (&mut a[12..]).copy_from_slice(&rng1.next_u32().to_le_bytes()); + a[..4].copy_from_slice(&rng1.next_u32().to_le_bytes()); + a[4..12].copy_from_slice(&rng1.next_u64().to_le_bytes()); + a[12..].copy_from_slice(&rng1.next_u32().to_le_bytes()); let mut b = [0; 16]; - (&mut b[..4]).copy_from_slice(&rng2.next_u32().to_le_bytes()); - (&mut b[4..8]).copy_from_slice(&rng2.next_u32().to_le_bytes()); - (&mut b[8..]).copy_from_slice(&rng2.next_u64().to_le_bytes()); + b[..4].copy_from_slice(&rng2.next_u32().to_le_bytes()); + b[4..8].copy_from_slice(&rng2.next_u32().to_le_bytes()); + b[8..].copy_from_slice(&rng2.next_u64().to_le_bytes()); assert_eq!(a, b); let mut c = [0; 16]; - (&mut c[..8]).copy_from_slice(&rng3.next_u64().to_le_bytes()); - (&mut c[8..12]).copy_from_slice(&rng3.next_u32().to_le_bytes()); - (&mut c[12..]).copy_from_slice(&rng3.next_u32().to_le_bytes()); + c[..8].copy_from_slice(&rng3.next_u64().to_le_bytes()); + c[8..12].copy_from_slice(&rng3.next_u32().to_le_bytes()); + c[12..].copy_from_slice(&rng3.next_u32().to_le_bytes()); assert_eq!(a, c); } @@ -520,22 +520,22 @@ mod test { let mut rng3 = rng1.clone(); let mut a = [0; 16]; - (&mut a[..4]).copy_from_slice(&rng1.next_u32().to_le_bytes()); - (&mut a[4..12]).copy_from_slice(&rng1.next_u64().to_le_bytes()); - (&mut a[12..]).copy_from_slice(&rng1.next_u32().to_le_bytes()); + a[..4].copy_from_slice(&rng1.next_u32().to_le_bytes()); + a[4..12].copy_from_slice(&rng1.next_u64().to_le_bytes()); + a[12..].copy_from_slice(&rng1.next_u32().to_le_bytes()); let mut b = [0; 16]; - (&mut b[..4]).copy_from_slice(&rng2.next_u32().to_le_bytes()); - (&mut b[4..8]).copy_from_slice(&rng2.next_u32().to_le_bytes()); - (&mut b[8..]).copy_from_slice(&rng2.next_u64().to_le_bytes()); + b[..4].copy_from_slice(&rng2.next_u32().to_le_bytes()); + b[4..8].copy_from_slice(&rng2.next_u32().to_le_bytes()); + b[8..].copy_from_slice(&rng2.next_u64().to_le_bytes()); assert_ne!(a, b); assert_eq!(&a[..4], &b[..4]); assert_eq!(&a[4..12], &b[8..]); let mut c = [0; 16]; - (&mut c[..8]).copy_from_slice(&rng3.next_u64().to_le_bytes()); - (&mut c[8..12]).copy_from_slice(&rng3.next_u32().to_le_bytes()); - (&mut c[12..]).copy_from_slice(&rng3.next_u32().to_le_bytes()); + c[..8].copy_from_slice(&rng3.next_u64().to_le_bytes()); + c[8..12].copy_from_slice(&rng3.next_u32().to_le_bytes()); + c[12..].copy_from_slice(&rng3.next_u32().to_le_bytes()); assert_eq!(b, c); } } diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 4cd21fd8170..2d380c64688 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -127,7 +127,7 @@ impl Distribution for Binomial { let mut u: f64 = rng.gen(); let mut x = 0; - while u > r as f64 { + while u > r { u -= r; x += 1; if x > BINV_MAX_X { @@ -332,7 +332,7 @@ mod test { } let mean = results.iter().sum::() / results.len() as f64; - assert!((mean as f64 - expected_mean).abs() < expected_mean / 50.0); + assert!((mean - expected_mean).abs() < expected_mean / 50.0); let variance = results.iter().map(|x| (x - mean) * (x - mean)).sum::() / results.len() as f64; diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 9aff7e625f4..cd3e31b453f 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -142,7 +142,7 @@ mod test { let distr = Cauchy::new(m, s).unwrap(); let mut rng = crate::test::rng(353); for x in buf { - *x = rng.sample(&distr); + *x = rng.sample(distr); } } diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index b8b396dd443..6ee64a77d98 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -59,7 +59,7 @@ impl Geometric { /// Construct a new `Geometric` with the given shape parameter `p` /// (probability of success on each trial). pub fn new(p: f64) -> Result { - if !p.is_finite() || p < 0.0 || p > 1.0 { + if !p.is_finite() || !(0.0..=1.0).contains(&p) { Err(Error::InvalidProbability) } else if p == 0.0 || p >= 2.0 / 3.0 { Ok(Geometric { p, pi: p, k: 0 }) @@ -198,7 +198,7 @@ mod test { } let mean = results.iter().sum::() / results.len() as f64; - assert!((mean as f64 - expected_mean).abs() < expected_mean / 40.0); + assert!((mean - expected_mean).abs() < expected_mean / 40.0); let variance = results.iter().map(|x| (x - mean) * (x - mean)).sum::() / results.len() as f64; @@ -230,7 +230,7 @@ mod test { } let mean = results.iter().sum::() / results.len() as f64; - assert!((mean as f64 - expected_mean).abs() < expected_mean / 50.0); + assert!((mean - expected_mean).abs() < expected_mean / 50.0); let variance = results.iter().map(|x| (x - mean) * (x - mean)).sum::() / results.len() as f64; diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index a42a572d8d1..73a8e91c75e 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -244,8 +244,8 @@ impl Distribution for Hypergeometric { let mut u = rng.gen::(); while u > p && x < k as i64 { // the paper erroneously uses `until n < p`, which doesn't make any sense u -= p; - p *= ((n1 as i64 - x as i64) * (k as i64 - x as i64)) as f64; - p /= ((x as i64 + 1) * (n2 as i64 - k as i64 + 1 + x as i64)) as f64; + p *= ((n1 as i64 - x) * (k as i64 - x)) as f64; + p /= ((x + 1) * (n2 as i64 - k as i64 + 1 + x)) as f64; x += 1; } x @@ -397,7 +397,7 @@ mod test { } let mean = results.iter().sum::() / results.len() as f64; - assert!((mean as f64 - expected_mean).abs() < expected_mean / 50.0); + assert!((mean - expected_mean).abs() < expected_mean / 50.0); let variance = results.iter().map(|x| (x - mean) * (x - mean)).sum::() / results.len() as f64; diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index b1ba588ac8d..7c5ad971710 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -76,7 +76,7 @@ where { fn sample(&self, rng: &mut R) -> F where R: Rng + ?Sized { - let inv_gauss = rng.sample(&self.inverse_gaussian); + let inv_gauss = rng.sample(self.inverse_gaussian); self.beta * inv_gauss + inv_gauss.sqrt() * rng.sample(StandardNormal) } diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 146b4ead125..29ba413a0ac 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -247,7 +247,7 @@ mod tests { let mut rng = crate::test::rng(213); let mut buf = [0.0; 4]; for x in &mut buf { - *x = rng.sample(&skew_normal); + *x = rng.sample(skew_normal); } for value in buf.iter() { assert!(value.is_nan()); diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 676b79a5c10..78bd724d789 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -206,7 +206,7 @@ mod test { let distr = Bernoulli::new(0.4532).unwrap(); let mut buf = [false; 10]; for x in &mut buf { - *x = rng.sample(&distr); + *x = rng.sample(distr); } assert_eq!(buf, [ true, false, false, true, false, false, true, true, true, true diff --git a/src/distributions/float.rs b/src/distributions/float.rs index 54aebad4dc5..d4a10757261 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -268,7 +268,7 @@ mod tests { let mut rng = crate::test::rng(0x6f44f5646c2a7334); let mut buf = [zero; 3]; for x in &mut buf { - *x = rng.sample(&distr); + *x = rng.sample(distr); } assert_eq!(&buf, expected); } diff --git a/src/distributions/other.rs b/src/distributions/other.rs index a8e1bbd7963..3ecf00492c6 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -328,7 +328,7 @@ mod tests { let mut rng = crate::test::rng(807); let mut buf = [zero; 5]; for x in &mut buf { - *x = rng.sample(&distr); + *x = rng.sample(distr); } assert_eq!(&buf, expected); } diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index ccf7b6e8ac9..ace3a6b43ea 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -1676,6 +1676,7 @@ mod tests { #[test] fn test_uniform_from_std_range_bad_limits() { + #![allow(clippy::reversed_empty_ranges)] assert!(Uniform::try_from(100..10).is_err()); assert!(Uniform::try_from(100..100).is_err()); assert!(Uniform::try_from(100.0..10.0).is_err()); @@ -1695,6 +1696,7 @@ mod tests { #[test] fn test_uniform_from_std_range_inclusive_bad_limits() { + #![allow(clippy::reversed_empty_ranges)] assert!(Uniform::try_from(100..=10).is_err()); assert!(Uniform::try_from(100..=99).is_err()); assert!(Uniform::try_from(100.0..=10.0).is_err()); @@ -1760,6 +1762,6 @@ mod tests { assert_eq!(Uniform::new(1.0, 2.0).unwrap(), Uniform::new(1.0, 2.0).unwrap()); // To cover UniformInt - assert_eq!(Uniform::new(1 as u32, 2 as u32).unwrap(), Uniform::new(1 as u32, 2 as u32).unwrap()); + assert_eq!(Uniform::new(1_u32, 2_u32).unwrap(), Uniform::new(1_u32, 2_u32).unwrap()); } } diff --git a/src/lib.rs b/src/lib.rs index 69b6293b8bc..8ade2881d5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,6 +54,7 @@ #![allow( clippy::float_cmp, clippy::neg_cmp_op_on_partial_ord, + clippy::nonminimal_bool )] #[cfg(feature = "std")] extern crate std; diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 05f18d71b2e..7a97fa8aaf0 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -37,9 +37,9 @@ impl CoinFlipper { if self.flip_c_heads(c) { let numerator = 1 << c; - return self.gen_ratio(numerator, d); + self.gen_ratio(numerator, d) } else { - return false; + false } } diff --git a/src/seq/index.rs b/src/seq/index.rs index f29f72e1726..956ea60ed81 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -105,12 +105,12 @@ impl PartialEq for IndexVec { fn eq(&self, other: &IndexVec) -> bool { use self::IndexVec::*; match (self, other) { - (&U32(ref v1), &U32(ref v2)) => v1 == v2, - (&USize(ref v1), &USize(ref v2)) => v1 == v2, - (&U32(ref v1), &USize(ref v2)) => { + (U32(v1), U32(v2)) => v1 == v2, + (USize(v1), USize(v2)) => v1 == v2, + (U32(v1), USize(v2)) => { (v1.len() == v2.len()) && (v1.iter().zip(v2.iter()).all(|(x, y)| *x as usize == *y)) } - (&USize(ref v1), &U32(ref v2)) => { + (USize(v1), U32(v2)) => { (v1.len() == v2.len()) && (v1.iter().zip(v2.iter()).all(|(x, y)| *x == *y as usize)) } } From 14d036c60cb4e26ed297204e5d84701dcd501110 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Thu, 14 Dec 2023 22:05:17 +0100 Subject: [PATCH 328/443] Add example for using rand (#1347) --- rand_pcg/Cargo.toml | 1 + rand_pcg/src/lib.rs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 1d4e811a869..757cc8e326c 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -26,6 +26,7 @@ rand_core = { path = "../rand_core", version = "0.7.0" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] +rand = { path = "..", version = "0.9" } # This is for testing serde, unfortunately we can't specify feature-gated dev # deps yet, see: https://github.com/rust-lang/cargo/issues/1596 # Versions prior to 1.1.4 had incorrect minimal dependencies. diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index 86484f7633b..c1484c2d55e 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -32,14 +32,24 @@ //! //! To initialize a generator, use the [`SeedableRng`][rand_core::SeedableRng] trait: //! -//! ```rust +//! ``` //! use rand_core::{SeedableRng, RngCore}; //! use rand_pcg::Pcg64Mcg; //! //! let mut rng = Pcg64Mcg::seed_from_u64(0); -//! // Alternatively, you may use `Pcg64Mcg::from_entropy()`. //! let x: u32 = rng.next_u32(); //! ``` +//! +//! The functionality of this crate is implemented using traits from the `rand_core` crate, but you may use the `rand` +//! crate for further functionality to initialize the generator from various sources and to generate random values: +//! +//! ``` +//! use rand::{Rng, SeedableRng}; +//! use rand_pcg::Pcg64Mcg; +//! +//! let mut rng = Pcg64Mcg::from_entropy(); +//! let x: f64 = rng.gen(); +//! ``` #![doc( html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", From e9a27a8e68c1626fb190feab5a6ec47c5fa38356 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 30 Dec 2023 11:14:38 +0000 Subject: [PATCH 329/443] Trap weighted index overflow (#1353) * WeightedIndex: add test overflow (expected to panic) * WeightedIndex::new: trap overflow in release builds only * Introduce trait Weight * Update regarding nightly SIMD changes --- src/distributions/float.rs | 2 +- src/distributions/mod.rs | 2 +- src/distributions/other.rs | 6 ++- src/distributions/uniform.rs | 7 +-- src/distributions/utils.rs | 5 +- src/distributions/weighted_index.rs | 75 +++++++++++++++++++++++++++-- src/seq/mod.rs | 26 ++-------- 7 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/distributions/float.rs b/src/distributions/float.rs index d4a10757261..37b71612a1b 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -12,7 +12,7 @@ use crate::distributions::utils::{IntAsSIMD, FloatAsSIMD, FloatSIMDUtils}; use crate::distributions::{Distribution, Standard}; use crate::Rng; use core::mem; -#[cfg(feature = "simd_support")] use core::simd::*; +#[cfg(feature = "simd_support")] use core::simd::prelude::*; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index a923f879d22..5adb82f8111 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -126,7 +126,7 @@ pub use self::slice::Slice; #[doc(inline)] pub use self::uniform::Uniform; #[cfg(feature = "alloc")] -pub use self::weighted_index::{WeightedError, WeightedIndex}; +pub use self::weighted_index::{Weight, WeightedError, WeightedIndex}; #[allow(unused)] use crate::Rng; diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 3ecf00492c6..ebe3d57ed3f 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -22,7 +22,9 @@ use crate::Rng; use serde::{Serialize, Deserialize}; use core::mem::{self, MaybeUninit}; #[cfg(feature = "simd_support")] -use core::simd::*; +use core::simd::prelude::*; +#[cfg(feature = "simd_support")] +use core::simd::{LaneCount, MaskElement, SupportedLaneCount}; // ----- Sampling distributions ----- @@ -163,7 +165,7 @@ impl Distribution for Standard { /// Since most bits are unused you could also generate only as many bits as you need, i.e.: /// ``` /// #![feature(portable_simd)] -/// use std::simd::*; +/// use std::simd::prelude::*; /// use rand::prelude::*; /// let mut rng = thread_rng(); /// diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index ace3a6b43ea..879d8ea2582 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -119,7 +119,8 @@ use crate::{Rng, RngCore}; #[allow(unused_imports)] // rustc doesn't detect that this is actually used use crate::distributions::utils::Float; -#[cfg(feature = "simd_support")] use core::simd::*; +#[cfg(feature = "simd_support")] use core::simd::prelude::*; +#[cfg(feature = "simd_support")] use core::simd::{LaneCount, SupportedLaneCount}; /// Error type returned from [`Uniform::new`] and `new_inclusive`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -1433,7 +1434,7 @@ mod tests { (-::core::$f_scalar::MAX * 0.2, ::core::$f_scalar::MAX * 0.7), ]; for &(low_scalar, high_scalar) in v.iter() { - for lane in 0..<$ty>::LANES { + for lane in 0..<$ty>::LEN { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); let my_uniform = Uniform::new(low, high).unwrap(); @@ -1565,7 +1566,7 @@ mod tests { (::std::$f_scalar::NEG_INFINITY, ::std::$f_scalar::INFINITY), ]; for &(low_scalar, high_scalar) in v.iter() { - for lane in 0..<$ty>::LANES { + for lane in 0..<$ty>::LEN { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); assert!(catch_unwind(|| range(low, high)).is_err()); diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index bddb0a4a599..f3b3089d7e1 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -8,7 +8,8 @@ //! Math helper functions -#[cfg(feature = "simd_support")] use core::simd::*; +#[cfg(feature = "simd_support")] use core::simd::prelude::*; +#[cfg(feature = "simd_support")] use core::simd::{LaneCount, SimdElement, SupportedLaneCount}; pub(crate) trait WideningMultiply { @@ -245,7 +246,7 @@ pub(crate) trait Float: Sized { /// Implement functions on f32/f64 to give them APIs similar to SIMD types pub(crate) trait FloatAsSIMD: Sized { - const LANES: usize = 1; + const LEN: usize = 1; #[inline(always)] fn splat(scalar: Self) -> Self { scalar diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 4c57edc5f60..de3628b5ead 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -99,12 +99,12 @@ impl WeightedIndex { where I: IntoIterator, I::Item: SampleBorrow, - X: for<'a> ::core::ops::AddAssign<&'a X> + Clone + Default, + X: Weight, { let mut iter = weights.into_iter(); let mut total_weight: X = iter.next().ok_or(WeightedError::NoItem)?.borrow().clone(); - let zero = ::default(); + let zero = X::ZERO; if !(total_weight >= zero) { return Err(WeightedError::InvalidWeight); } @@ -117,7 +117,10 @@ impl WeightedIndex { return Err(WeightedError::InvalidWeight); } weights.push(total_weight.clone()); - total_weight += w.borrow(); + + if let Err(()) = total_weight.checked_add_assign(w.borrow()) { + return Err(WeightedError::Overflow); + } } if total_weight == zero { @@ -236,6 +239,60 @@ where X: SampleUniform + PartialOrd } } +/// Bounds on a weight +/// +/// See usage in [`WeightedIndex`]. +pub trait Weight: Clone { + /// Representation of 0 + const ZERO: Self; + + /// Checked addition + /// + /// - `Result::Ok`: On success, `v` is added to `self` + /// - `Result::Err`: Returns an error when `Self` cannot represent the + /// result of `self + v` (i.e. overflow). The value of `self` should be + /// discarded. + fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()>; +} + +macro_rules! impl_weight_int { + ($t:ty) => { + impl Weight for $t { + const ZERO: Self = 0; + fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()> { + match self.checked_add(*v) { + Some(sum) => { + *self = sum; + Ok(()) + } + None => Err(()), + } + } + } + }; + ($t:ty, $($tt:ty),*) => { + impl_weight_int!($t); + impl_weight_int!($($tt),*); + } +} +impl_weight_int!(i8, i16, i32, i64, i128, isize); +impl_weight_int!(u8, u16, u32, u64, u128, usize); + +macro_rules! impl_weight_float { + ($t:ty) => { + impl Weight for $t { + const ZERO: Self = 0.0; + fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()> { + // Floats have an explicit representation for overflow + *self += *v; + Ok(()) + } + } + } +} +impl_weight_float!(f32); +impl_weight_float!(f64); + #[cfg(test)] mod test { use super::*; @@ -388,12 +445,11 @@ mod test { #[test] fn value_stability() { - fn test_samples( + fn test_samples( weights: I, buf: &mut [usize], expected: &[usize], ) where I: IntoIterator, I::Item: SampleBorrow, - X: for<'a> ::core::ops::AddAssign<&'a X> + Clone + Default, { assert_eq!(buf.len(), expected.len()); let distr = WeightedIndex::new(weights).unwrap(); @@ -420,6 +476,11 @@ mod test { fn weighted_index_distributions_can_be_compared() { assert_eq!(WeightedIndex::new(&[1, 2]), WeightedIndex::new(&[1, 2])); } + + #[test] + fn overflow() { + assert_eq!(WeightedIndex::new([2, usize::MAX]), Err(WeightedError::Overflow)); + } } /// Error type returned from `WeightedIndex::new`. @@ -438,6 +499,9 @@ pub enum WeightedError { /// Too many weights are provided (length greater than `u32::MAX`) TooMany, + + /// The sum of weights overflows + Overflow, } #[cfg(feature = "std")] @@ -450,6 +514,7 @@ impl fmt::Display for WeightedError { WeightedError::InvalidWeight => "A weight is invalid in distribution", WeightedError::AllWeightsZero => "All weights are zero in distribution", WeightedError::TooMany => "Too many weights (hit u32::MAX) in distribution", + WeightedError::Overflow => "The sum of weights overflowed", }) } } diff --git a/src/seq/mod.rs b/src/seq/mod.rs index bbb46fc55fe..9012b21b909 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -40,7 +40,7 @@ use alloc::vec::Vec; #[cfg(feature = "alloc")] use crate::distributions::uniform::{SampleBorrow, SampleUniform}; #[cfg(feature = "alloc")] -use crate::distributions::WeightedError; +use crate::distributions::{Weight, WeightedError}; use crate::Rng; use self::coin_flipper::CoinFlipper; @@ -170,11 +170,7 @@ pub trait SliceRandom { R: Rng + ?Sized, F: Fn(&Self::Item) -> B, B: SampleBorrow, - X: SampleUniform - + for<'a> ::core::ops::AddAssign<&'a X> - + ::core::cmp::PartialOrd - + Clone - + Default; + X: SampleUniform + Weight + ::core::cmp::PartialOrd; /// Biased sampling for one element (mut) /// @@ -203,11 +199,7 @@ pub trait SliceRandom { R: Rng + ?Sized, F: Fn(&Self::Item) -> B, B: SampleBorrow, - X: SampleUniform - + for<'a> ::core::ops::AddAssign<&'a X> - + ::core::cmp::PartialOrd - + Clone - + Default; + X: SampleUniform + Weight + ::core::cmp::PartialOrd; /// Biased sampling of `amount` distinct elements /// @@ -585,11 +577,7 @@ impl SliceRandom for [T] { R: Rng + ?Sized, F: Fn(&Self::Item) -> B, B: SampleBorrow, - X: SampleUniform - + for<'a> ::core::ops::AddAssign<&'a X> - + ::core::cmp::PartialOrd - + Clone - + Default, + X: SampleUniform + Weight + ::core::cmp::PartialOrd, { use crate::distributions::{Distribution, WeightedIndex}; let distr = WeightedIndex::new(self.iter().map(weight))?; @@ -604,11 +592,7 @@ impl SliceRandom for [T] { R: Rng + ?Sized, F: Fn(&Self::Item) -> B, B: SampleBorrow, - X: SampleUniform - + for<'a> ::core::ops::AddAssign<&'a X> - + ::core::cmp::PartialOrd - + Clone - + Default, + X: SampleUniform + Weight + ::core::cmp::PartialOrd, { use crate::distributions::{Distribution, WeightedIndex}; let distr = WeightedIndex::new(self.iter().map(weight))?; From 1db3aa416ce4bc0e4792a0e6aa98f009ef483604 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 5 Jan 2024 10:58:22 +0000 Subject: [PATCH 330/443] Replace SeedableRng impl for SmallRng with inherent fns (#1368) * Simplify redundant doc-link * Remove impl of SeedableRng for SmallRng * Fix SeedableRng::Seed type for StdRng * SmallRng: do not expect thread_rng * Make SmallRng::from_thread_rng infallible * Fix benchmarks (SmallRng does not have from_entropy) --- benches/generators.rs | 6 +-- benches/uniform.rs | 4 +- benches/uniform_float.rs | 4 +- rand_core/src/block.rs | 2 +- src/rngs/small.rs | 96 +++++++++++++++++++--------------------- src/rngs/std.rs | 3 +- 6 files changed, 55 insertions(+), 60 deletions(-) diff --git a/benches/generators.rs b/benches/generators.rs index 96fa302b6a0..12b8460f0b5 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -50,7 +50,7 @@ gen_bytes!(gen_bytes_chacha12, ChaCha12Rng::from_entropy()); gen_bytes!(gen_bytes_chacha20, ChaCha20Rng::from_entropy()); gen_bytes!(gen_bytes_std, StdRng::from_entropy()); #[cfg(feature = "small_rng")] -gen_bytes!(gen_bytes_small, SmallRng::from_entropy()); +gen_bytes!(gen_bytes_small, SmallRng::from_thread_rng()); gen_bytes!(gen_bytes_os, OsRng); macro_rules! gen_uint { @@ -80,7 +80,7 @@ gen_uint!(gen_u32_chacha12, u32, ChaCha12Rng::from_entropy()); gen_uint!(gen_u32_chacha20, u32, ChaCha20Rng::from_entropy()); gen_uint!(gen_u32_std, u32, StdRng::from_entropy()); #[cfg(feature = "small_rng")] -gen_uint!(gen_u32_small, u32, SmallRng::from_entropy()); +gen_uint!(gen_u32_small, u32, SmallRng::from_thread_rng()); gen_uint!(gen_u32_os, u32, OsRng); gen_uint!(gen_u64_step, u64, StepRng::new(0, 1)); @@ -93,7 +93,7 @@ gen_uint!(gen_u64_chacha12, u64, ChaCha12Rng::from_entropy()); gen_uint!(gen_u64_chacha20, u64, ChaCha20Rng::from_entropy()); gen_uint!(gen_u64_std, u64, StdRng::from_entropy()); #[cfg(feature = "small_rng")] -gen_uint!(gen_u64_small, u64, SmallRng::from_entropy()); +gen_uint!(gen_u64_small, u64, SmallRng::from_thread_rng()); gen_uint!(gen_u64_os, u64, OsRng); macro_rules! init_gen { diff --git a/benches/uniform.rs b/benches/uniform.rs index d0128d5a48e..0ed0f2cde40 100644 --- a/benches/uniform.rs +++ b/benches/uniform.rs @@ -23,7 +23,7 @@ const N_RESAMPLES: usize = 10_000; macro_rules! sample { ($R:ty, $T:ty, $U:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($R), "single"), |b| { - let mut rng = <$R>::from_entropy(); + let mut rng = <$R>::from_rng(thread_rng()).unwrap(); let x = rng.gen::<$U>(); let bits = (<$T>::BITS / 2); let mask = (1 as $U).wrapping_neg() >> bits; @@ -35,7 +35,7 @@ macro_rules! sample { }); $g.bench_function(BenchmarkId::new(stringify!($R), "distr"), |b| { - let mut rng = <$R>::from_entropy(); + let mut rng = <$R>::from_rng(thread_rng()).unwrap(); let x = rng.gen::<$U>(); let bits = (<$T>::BITS / 2); let mask = (1 as $U).wrapping_neg() >> bits; diff --git a/benches/uniform_float.rs b/benches/uniform_float.rs index 3366ad3f986..957ff1b8ecf 100644 --- a/benches/uniform_float.rs +++ b/benches/uniform_float.rs @@ -27,7 +27,7 @@ const N_RESAMPLES: usize = 10_000; macro_rules! single_random { ($R:ty, $T:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { - let mut rng = <$R>::from_entropy(); + let mut rng = <$R>::from_rng(thread_rng()).unwrap(); let (mut low, mut high); loop { low = <$T>::from_bits(rng.gen()); @@ -63,7 +63,7 @@ fn single_random(c: &mut Criterion) { macro_rules! distr_random { ($R:ty, $T:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { - let mut rng = <$R>::from_entropy(); + let mut rng = <$R>::from_rng(thread_rng()).unwrap(); let dist = loop { let low = <$T>::from_bits(rng.gen()); let high = <$T>::from_bits(rng.gen()); diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 387e7aa3958..a8cefc8e40c 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -80,7 +80,7 @@ pub trait BlockRngCore { /// A marker trait used to indicate that an [`RngCore`] implementation is /// supposed to be cryptographically secure. /// -/// See [`CryptoRng`][crate::CryptoRng] docs for more information. +/// See [`CryptoRng`] docs for more information. pub trait CryptoBlockRng: BlockRngCore { } /// A wrapper type implementing [`RngCore`] for some type implementing diff --git a/src/rngs/small.rs b/src/rngs/small.rs index a3261757847..2841b0b5dd8 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -22,18 +22,12 @@ type Rng = super::xoshiro128plusplus::Xoshiro128PlusPlus; /// Note that depending on the application, [`StdRng`] may be faster on many /// modern platforms while providing higher-quality randomness. Furthermore, /// `SmallRng` is **not** a good choice when: -/// - Security against prediction is important. Use [`StdRng`] instead. -/// - Seeds with many zeros are provided. In such cases, it takes `SmallRng` -/// about 10 samples to produce 0 and 1 bits with equal probability. Either -/// provide seeds with an approximately equal number of 0 and 1 (for example -/// by using [`SeedableRng::from_entropy`] or [`SeedableRng::seed_from_u64`]), -/// or use [`StdRng`] instead. /// -/// The algorithm is deterministic but should not be considered reproducible -/// due to dependence on platform and possible replacement in future -/// library versions. For a reproducible generator, use a named PRNG from an -/// external crate, e.g. [rand_xoshiro] or [rand_chacha]. -/// Refer also to [The Book](https://rust-random.github.io/book/guide-rngs.html). +/// - Portability is required. Its implementation is not fixed. Use a named +/// generator from an external crate instead, for example [rand_xoshiro] or +/// [rand_chacha]. Refer also to +/// [The Book](https://rust-random.github.io/book/guide-rngs.html). +/// - Security against prediction is important. Use [`StdRng`] instead. /// /// The PRNG algorithm in `SmallRng` is chosen to be efficient on the current /// platform, without consideration for cryptography or security. The size of @@ -41,39 +35,7 @@ type Rng = super::xoshiro128plusplus::Xoshiro128PlusPlus; /// `Xoshiro256PlusPlus` on 64-bit platforms and `Xoshiro128PlusPlus` on 32-bit /// platforms. Both are also implemented by the [rand_xoshiro] crate. /// -/// # Examples -/// -/// Initializing `SmallRng` with a random seed can be done using [`SeedableRng::from_entropy`]: -/// -/// ``` -/// use rand::{Rng, SeedableRng}; -/// use rand::rngs::SmallRng; -/// -/// // Create small, cheap to initialize and fast RNG with a random seed. -/// // The randomness is supplied by the operating system. -/// let mut small_rng = SmallRng::from_entropy(); -/// # let v: u32 = small_rng.gen(); -/// ``` -/// -/// When initializing a lot of `SmallRng`'s, using [`thread_rng`] can be more -/// efficient: -/// -/// ``` -/// use rand::{SeedableRng, thread_rng}; -/// use rand::rngs::SmallRng; -/// -/// // Create a big, expensive to initialize and slower, but unpredictable RNG. -/// // This is cached and done only once per thread. -/// let mut thread_rng = thread_rng(); -/// // Create small, cheap to initialize and fast RNGs with random seeds. -/// // One can generally assume this won't fail. -/// let rngs: Vec = (0..10) -/// .map(|_| SmallRng::from_rng(&mut thread_rng).unwrap()) -/// .collect(); -/// ``` -/// /// [`StdRng`]: crate::rngs::StdRng -/// [`thread_rng`]: crate::thread_rng /// [rand_chacha]: https://crates.io/crates/rand_chacha /// [rand_xoshiro]: https://crates.io/crates/rand_xoshiro #[cfg_attr(doc_cfg, doc(cfg(feature = "small_rng")))] @@ -102,21 +64,53 @@ impl RngCore for SmallRng { } } -impl SeedableRng for SmallRng { - type Seed = ::Seed; - +impl SmallRng { + /// Construct an instance seeded from another `Rng` + /// + /// We recommend that the source (master) RNG uses a different algorithm + /// (i.e. is not `SmallRng`) to avoid correlations between the child PRNGs. + /// + /// # Example + /// ``` + /// # use rand::rngs::SmallRng; + /// let rng = SmallRng::from_rng(rand::thread_rng()); + /// ``` #[inline(always)] - fn from_seed(seed: Self::Seed) -> Self { - SmallRng(Rng::from_seed(seed)) + pub fn from_rng(rng: R) -> Result { + Rng::from_rng(rng).map(SmallRng) } + /// Construct an instance seeded from the thread-local RNG + /// + /// # Panics + /// + /// This method panics only if [`thread_rng`](crate::thread_rng) fails to + /// initialize. + #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] #[inline(always)] - fn from_rng(rng: R) -> Result { - Rng::from_rng(rng).map(SmallRng) + pub fn from_thread_rng() -> Self { + let mut seed = ::Seed::default(); + crate::thread_rng().fill_bytes(seed.as_mut()); + SmallRng(Rng::from_seed(seed)) } + /// Construct an instance from a `u64` seed + /// + /// This provides a convenient method of seeding a `SmallRng` from a simple + /// number by use of another algorithm to mutate and expand the input. + /// This is suitable for use with low Hamming Weight numbers like 0 and 1. + /// + /// **Warning:** the implementation is deterministic but not portable: + /// output values may differ according to platform and may be changed by a + /// future version of the library. + /// + /// # Example + /// ``` + /// # use rand::rngs::SmallRng; + /// let rng = SmallRng::seed_from_u64(1); + /// ``` #[inline(always)] - fn seed_from_u64(state: u64) -> Self { + pub fn seed_from_u64(state: u64) -> Self { SmallRng(Rng::seed_from_u64(state)) } } diff --git a/src/rngs/std.rs b/src/rngs/std.rs index ddc3014dfe1..31b20a2dc5d 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -57,7 +57,8 @@ impl RngCore for StdRng { } impl SeedableRng for StdRng { - type Seed = ::Seed; + // Fix to 256 bits. Changing this is a breaking change! + type Seed = [u8; 32]; #[inline(always)] fn from_seed(seed: Self::Seed) -> Self { From 1f4507a8e1cf8050e4ceef95eeda8f64645b6719 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:15:32 +0100 Subject: [PATCH 331/443] Allow new warning on recent nightly versions (#1376) --- rand_core/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index a5a7fb150c6..292c57ffb8b 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -245,6 +245,7 @@ pub trait SeedableRng: Sized { /// /// const N: usize = 64; /// pub struct MyRngSeed(pub [u8; N]); + /// # #[allow(dead_code)] /// pub struct MyRng(MyRngSeed); /// /// impl Default for MyRngSeed { From f827c006b52676301add803671b5d063191dcdad Mon Sep 17 00:00:00 2001 From: Makro <4398091+xmakro@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:00:58 +0000 Subject: [PATCH 332/443] Add WeightedIndexTree to dist_randr (#1372) Add WeightedIndexTree to rand_distr Co-authored-by: xmakro --- rand_distr/src/lib.rs | 9 +- rand_distr/src/weighted_tree.rs | 384 ++++++++++++++++++++++++++++ src/distributions/weighted_index.rs | 63 +++-- 3 files changed, 433 insertions(+), 23 deletions(-) create mode 100644 rand_distr/src/weighted_tree.rs diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index c8fd298171d..1e28aaaa79a 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -76,8 +76,9 @@ //! - [`UnitBall`] distribution //! - [`UnitCircle`] distribution //! - [`UnitDisc`] distribution -//! - Alternative implementation for weighted index sampling +//! - Alternative implementations for weighted index sampling //! - [`WeightedAliasIndex`] distribution +//! - [`WeightedTreeIndex`] distribution //! - Misc. distributions //! - [`InverseGaussian`] distribution //! - [`NormalInverseGaussian`] distribution @@ -133,6 +134,9 @@ pub use rand::distributions::{WeightedError, WeightedIndex}; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use weighted_alias::WeightedAliasIndex; +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +pub use weighted_tree::WeightedTreeIndex; pub use num_traits; @@ -186,6 +190,9 @@ mod test { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod weighted_alias; +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +pub mod weighted_tree; mod binomial; mod cauchy; diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs new file mode 100644 index 00000000000..b308cdb2c04 --- /dev/null +++ b/rand_distr/src/weighted_tree.rs @@ -0,0 +1,384 @@ +// Copyright 2024 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This module contains an implementation of a tree structure for sampling random +//! indices with probabilities proportional to a collection of weights. + +use core::ops::SubAssign; + +use super::WeightedError; +use crate::Distribution; +use alloc::vec::Vec; +use rand::distributions::uniform::{SampleBorrow, SampleUniform}; +use rand::distributions::Weight; +use rand::Rng; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// A distribution using weighted sampling to pick a discretely selected item. +/// +/// Sampling a [`WeightedTreeIndex`] distribution returns the index of a randomly +/// selected element from the vector used to create the [`WeightedTreeIndex`]. +/// The chance of a given element being picked is proportional to the value of +/// the element. The weights can have any type `W` for which an implementation of +/// [`Weight`] exists. +/// +/// # Key differences +/// +/// The main distinction between [`WeightedTreeIndex`] and [`rand::distributions::WeightedIndex`] +/// lies in the internal representation of weights. In [`WeightedTreeIndex`], +/// weights are structured as a tree, which is optimized for frequent updates of the weights. +/// +/// # Caution: Floating point types +/// +/// When utilizing [`WeightedTreeIndex`] with floating point types (such as f32 or f64), +/// exercise caution due to the inherent nature of floating point arithmetic. Floating point types +/// are susceptible to numerical rounding errors. Since operations on floating point weights are +/// repeated numerous times, rounding errors can accumulate, potentially leading to noticeable +/// deviations from the expected behavior. +/// +/// Ideally, use fixed point or integer types whenever possible. +/// +/// # Performance +/// +/// A [`WeightedTreeIndex`] with `n` elements requires `O(n)` memory. +/// +/// Time complexity for the operations of a [`WeightedTreeIndex`] are: +/// * Constructing: Building the initial tree from an iterator of weights takes `O(n)` time. +/// * Sampling: Choosing an index (traversing down the tree) requires `O(log n)` time. +/// * Weight Update: Modifying a weight (traversing up the tree), requires `O(log n)` time. +/// * Weight Addition (Pushing): Adding a new weight (traversing up the tree), requires `O(log n)` time. +/// * Weight Removal (Popping): Removing a weight (traversing up the tree), requires `O(log n)` time. +/// +/// # Example +/// +/// ``` +/// use rand_distr::WeightedTreeIndex; +/// use rand::prelude::*; +/// +/// let choices = vec!['a', 'b', 'c']; +/// let weights = vec![2, 0]; +/// let mut dist = WeightedTreeIndex::new(&weights).unwrap(); +/// dist.push(1).unwrap(); +/// dist.update(1, 1).unwrap(); +/// let mut rng = thread_rng(); +/// let mut samples = [0; 3]; +/// for _ in 0..100 { +/// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c' +/// let i = dist.sample(&mut rng); +/// samples[i] += 1; +/// } +/// println!("Results: {:?}", choices.iter().zip(samples.iter()).collect::>()); +/// ``` +/// +/// [`WeightedTreeIndex`]: WeightedTreeIndex +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr( + feature = "serde1", + serde(bound(serialize = "W: Serialize, W::Sampler: Serialize")) +)] +#[cfg_attr( + feature = "serde1 ", + serde(bound(deserialize = "W: Deserialize<'de>, W::Sampler: Deserialize<'de>")) +)] +#[derive(Clone, Default, Debug, PartialEq)] +pub struct WeightedTreeIndex< + W: Clone + PartialEq + PartialOrd + SampleUniform + SubAssign + Weight, +> { + subtotals: Vec, +} + +impl + Weight> + WeightedTreeIndex +{ + /// Creates a new [`WeightedTreeIndex`] from a slice of weights. + pub fn new(weights: I) -> Result + where + I: IntoIterator, + I::Item: SampleBorrow, + { + let mut subtotals: Vec = weights.into_iter().map(|x| x.borrow().clone()).collect(); + for weight in subtotals.iter() { + if *weight < W::ZERO { + return Err(WeightedError::InvalidWeight); + } + } + let n = subtotals.len(); + for i in (1..n).rev() { + let w = subtotals[i].clone(); + let parent = (i - 1) / 2; + subtotals[parent] + .checked_add_assign(&w) + .map_err(|()| WeightedError::Overflow)?; + } + Ok(Self { subtotals }) + } + + /// Returns `true` if the tree contains no weights. + pub fn is_empty(&self) -> bool { + self.subtotals.is_empty() + } + + /// Returns the number of weights. + pub fn len(&self) -> usize { + self.subtotals.len() + } + + /// Returns `true` if we can sample. + /// + /// This is the case if the total weight of the tree is greater than zero. + pub fn is_valid(&self) -> bool { + if let Some(weight) = self.subtotals.first() { + *weight > W::ZERO + } else { + false + } + } + + /// Gets the weight at an index. + pub fn get(&self, index: usize) -> W { + let left_index = 2 * index + 1; + let right_index = 2 * index + 2; + let mut w = self.subtotals[index].clone(); + w -= self.subtotal(left_index); + w -= self.subtotal(right_index); + w + } + + /// Removes the last weight and returns it, or [`None`] if it is empty. + pub fn pop(&mut self) -> Option { + self.subtotals.pop().map(|weight| { + let mut index = self.len(); + while index != 0 { + index = (index - 1) / 2; + self.subtotals[index] -= weight.clone(); + } + weight + }) + } + + /// Appends a new weight at the end. + pub fn push(&mut self, weight: W) -> Result<(), WeightedError> { + if weight < W::ZERO { + return Err(WeightedError::InvalidWeight); + } + if let Some(total) = self.subtotals.first() { + let mut total = total.clone(); + if total.checked_add_assign(&weight).is_err() { + return Err(WeightedError::Overflow); + } + } + let mut index = self.len(); + self.subtotals.push(weight.clone()); + while index != 0 { + index = (index - 1) / 2; + self.subtotals[index].checked_add_assign(&weight).unwrap(); + } + Ok(()) + } + + /// Updates the weight at an index. + pub fn update(&mut self, mut index: usize, weight: W) -> Result<(), WeightedError> { + if weight < W::ZERO { + return Err(WeightedError::InvalidWeight); + } + let old_weight = self.get(index); + if weight > old_weight { + let mut difference = weight; + difference -= old_weight; + if let Some(total) = self.subtotals.first() { + let mut total = total.clone(); + if total.checked_add_assign(&difference).is_err() { + return Err(WeightedError::Overflow); + } + } + self.subtotals[index] + .checked_add_assign(&difference) + .unwrap(); + while index != 0 { + index = (index - 1) / 2; + self.subtotals[index] + .checked_add_assign(&difference) + .unwrap(); + } + } else if weight < old_weight { + let mut difference = old_weight; + difference -= weight; + self.subtotals[index] -= difference.clone(); + while index != 0 { + index = (index - 1) / 2; + self.subtotals[index] -= difference.clone(); + } + } + Ok(()) + } + + fn subtotal(&self, index: usize) -> W { + if index < self.subtotals.len() { + self.subtotals[index].clone() + } else { + W::ZERO + } + } +} + +impl + Weight> + WeightedTreeIndex +{ + /// Samples a randomly selected index from the weighted distribution. + /// + /// Returns an error if there are no elements or all weights are zero. This + /// is unlike [`Distribution::sample`], which panics in those cases. + fn try_sample(&self, rng: &mut R) -> Result { + if self.subtotals.is_empty() { + return Err(WeightedError::NoItem); + } + let total_weight = self.subtotals[0].clone(); + if total_weight == W::ZERO { + return Err(WeightedError::AllWeightsZero); + } + let mut target_weight = rng.gen_range(W::ZERO..total_weight); + let mut index = 0; + loop { + // Maybe descend into the left sub tree. + let left_index = 2 * index + 1; + let left_subtotal = self.subtotal(left_index); + if target_weight < left_subtotal { + index = left_index; + continue; + } + target_weight -= left_subtotal; + + // Maybe descend into the right sub tree. + let right_index = 2 * index + 2; + let right_subtotal = self.subtotal(right_index); + if target_weight < right_subtotal { + index = right_index; + continue; + } + target_weight -= right_subtotal; + + // Otherwise we found the index with the target weight. + break; + } + assert!(target_weight >= W::ZERO); + assert!(target_weight < self.get(index)); + Ok(index) + } +} + +/// Samples a randomly selected index from the weighted distribution. +/// +/// Caution: This method panics if there are no elements or all weights are zero. However, +/// it is guaranteed that this method will not panic if a call to [`WeightedTreeIndex::is_valid`] +/// returns `true`. +impl + Weight> Distribution + for WeightedTreeIndex +{ + fn sample(&self, rng: &mut R) -> usize { + self.try_sample(rng).unwrap() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_no_item_error() { + let mut rng = crate::test::rng(0x9c9fa0b0580a7031); + let tree = WeightedTreeIndex::::new(&[]).unwrap(); + assert_eq!( + tree.try_sample(&mut rng).unwrap_err(), + WeightedError::NoItem + ); + } + + #[test] + fn test_overflow_error() { + assert_eq!( + WeightedTreeIndex::new(&[i32::MAX, 2]), + Err(WeightedError::Overflow) + ); + let mut tree = WeightedTreeIndex::new(&[i32::MAX - 2, 1]).unwrap(); + assert_eq!(tree.push(3), Err(WeightedError::Overflow)); + assert_eq!(tree.update(1, 4), Err(WeightedError::Overflow)); + tree.update(1, 2).unwrap(); + } + + #[test] + fn test_all_weights_zero_error() { + let tree = WeightedTreeIndex::::new(&[0.0, 0.0]).unwrap(); + let mut rng = crate::test::rng(0x9c9fa0b0580a7031); + assert_eq!( + tree.try_sample(&mut rng).unwrap_err(), + WeightedError::AllWeightsZero + ); + } + + #[test] + fn test_invalid_weight_error() { + assert_eq!( + WeightedTreeIndex::::new(&[1, -1]).unwrap_err(), + WeightedError::InvalidWeight + ); + let mut tree = WeightedTreeIndex::::new(&[]).unwrap(); + assert_eq!(tree.push(-1).unwrap_err(), WeightedError::InvalidWeight); + tree.push(1).unwrap(); + assert_eq!( + tree.update(0, -1).unwrap_err(), + WeightedError::InvalidWeight + ); + } + + #[test] + fn test_tree_modifications() { + let mut tree = WeightedTreeIndex::new(&[9, 1, 2]).unwrap(); + tree.push(3).unwrap(); + tree.push(5).unwrap(); + tree.update(0, 0).unwrap(); + assert_eq!(tree.pop(), Some(5)); + let expected = WeightedTreeIndex::new(&[0, 1, 2, 3]).unwrap(); + assert_eq!(tree, expected); + } + + #[test] + fn test_sample_counts_match_probabilities() { + let start = 1; + let end = 3; + let samples = 20; + let mut rng = crate::test::rng(0x9c9fa0b0580a7031); + let weights: Vec<_> = (0..end).map(|_| rng.gen()).collect(); + let mut tree = WeightedTreeIndex::new(&weights).unwrap(); + let mut total_weight = 0.0; + let mut weights = alloc::vec![0.0; end]; + for i in 0..end { + tree.update(i, i as f64).unwrap(); + weights[i] = i as f64; + total_weight += i as f64; + } + for i in 0..start { + tree.update(i, 0.0).unwrap(); + weights[i] = 0.0; + total_weight -= i as f64; + } + let mut counts = alloc::vec![0_usize; end]; + for _ in 0..samples { + let i = tree.sample(&mut rng); + counts[i] += 1; + } + for i in 0..start { + assert_eq!(counts[i], 0); + } + for i in start..end { + let diff = counts[i] as f64 / samples as f64 - weights[i] / total_weight; + assert!(diff.abs() < 0.05); + } + } +} diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index de3628b5ead..0b1b4da947c 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -18,7 +18,7 @@ use core::fmt; use alloc::vec::Vec; #[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; /// A distribution using weighted sampling of discrete items /// @@ -33,9 +33,12 @@ use serde::{Serialize, Deserialize}; /// # Performance /// /// Time complexity of sampling from `WeightedIndex` is `O(log N)` where -/// `N` is the number of weights. As an alternative, -/// [`rand_distr::weighted_alias`](https://docs.rs/rand_distr/*/rand_distr/weighted_alias/index.html) +/// `N` is the number of weights. There are two alternative implementations with +/// different runtimes characteristics: +/// * [`rand_distr::weighted_alias`](https://docs.rs/rand_distr/*/rand_distr/weighted_alias/index.html) /// supports `O(1)` sampling, but with much higher initialisation cost. +/// * [`rand_distr::weighted_tree`](https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html) +/// keeps the weights in a tree structure where sampling and updating is `O(log N)`. /// /// A `WeightedIndex` contains a `Vec` and a [`Uniform`] and so its /// size is the sum of the size of those objects, possibly plus some alignment. @@ -144,15 +147,21 @@ impl WeightedIndex { /// allocation internally. /// /// In case of error, `self` is not modified. - /// + /// + /// Updates take `O(N)` time. If you need to frequently update weights, consider + /// [`rand_distr::weighted_tree`](https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html) + /// as an alternative where an update is `O(log N)`. + /// /// Note: Updating floating-point weights may cause slight inaccuracies in the total weight. /// This method may not return `WeightedError::AllWeightsZero` when all weights - /// are zero if using floating-point weights. + /// are zero if using floating-point weights. pub fn update_weights(&mut self, new_weights: &[(usize, &X)]) -> Result<(), WeightedError> - where X: for<'a> ::core::ops::AddAssign<&'a X> + where + X: for<'a> ::core::ops::AddAssign<&'a X> + for<'a> ::core::ops::SubAssign<&'a X> + Clone - + Default { + + Default, + { if new_weights.is_empty() { return Ok(()); } @@ -230,12 +239,14 @@ impl WeightedIndex { } impl Distribution for WeightedIndex -where X: SampleUniform + PartialOrd +where + X: SampleUniform + PartialOrd, { fn sample(&self, rng: &mut R) -> usize { let chosen_weight = self.weight_distribution.sample(rng); // Find the first item which has a weight *higher* than the chosen weight. - self.cumulative_weights.partition_point(|w| w <= &chosen_weight) + self.cumulative_weights + .partition_point(|w| w <= &chosen_weight) } } @@ -288,7 +299,7 @@ macro_rules! impl_weight_float { Ok(()) } } - } + }; } impl_weight_float!(f32); impl_weight_float!(f64); @@ -314,7 +325,7 @@ mod test { } #[test] - fn test_accepting_nan(){ + fn test_accepting_nan() { assert_eq!( WeightedIndex::new(&[core::f32::NAN, 0.5]).unwrap_err(), WeightedError::InvalidWeight, @@ -337,7 +348,6 @@ mod test { ) } - #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_weightedindex() { @@ -461,15 +471,21 @@ mod test { } let mut buf = [0; 10]; - test_samples(&[1i32, 1, 1, 1, 1, 1, 1, 1, 1], &mut buf, &[ - 0, 6, 2, 6, 3, 4, 7, 8, 2, 5, - ]); - test_samples(&[0.7f32, 0.1, 0.1, 0.1], &mut buf, &[ - 0, 0, 0, 1, 0, 0, 2, 3, 0, 0, - ]); - test_samples(&[1.0f64, 0.999, 0.998, 0.997], &mut buf, &[ - 2, 2, 1, 3, 2, 1, 3, 3, 2, 1, - ]); + test_samples( + &[1i32, 1, 1, 1, 1, 1, 1, 1, 1], + &mut buf, + &[0, 6, 2, 6, 3, 4, 7, 8, 2, 5], + ); + test_samples( + &[0.7f32, 0.1, 0.1, 0.1], + &mut buf, + &[0, 0, 0, 1, 0, 0, 2, 3, 0, 0], + ); + test_samples( + &[1.0f64, 0.999, 0.998, 0.997], + &mut buf, + &[2, 2, 1, 3, 2, 1, 3, 3, 2, 1], + ); } #[test] @@ -479,7 +495,10 @@ mod test { #[test] fn overflow() { - assert_eq!(WeightedIndex::new([2, usize::MAX]), Err(WeightedError::Overflow)); + assert_eq!( + WeightedIndex::new([2, usize::MAX]), + Err(WeightedError::Overflow) + ); } } From 88b2acca6749a7b4fc29e638c9b7b237c2d230da Mon Sep 17 00:00:00 2001 From: Vasiliy Taranov Date: Tue, 13 Feb 2024 10:56:18 +0300 Subject: [PATCH 333/443] Add dependabot (#1383) --- .github/dependabot.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..22b1e8da2f5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "monthly" + open-pull-requests-limit: 10 + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" From 1ad2eece9dc50d2a4e8a2b545fa532b128e57d4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:21:36 +0000 Subject: [PATCH 334/443] Bump actions/configure-pages from 2 to 4 (#1389) --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index f751f8c5d2d..9ef038b23c1 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -34,7 +34,7 @@ jobs: cp utils/redirect.html target/doc/index.html - name: Setup Pages - uses: actions/configure-pages@v2 + uses: actions/configure-pages@v4 - name: Upload artifact uses: actions/upload-pages-artifact@v1 From 58add64ba4baf2102b39f9bf3be48ff6e8262452 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:39:03 +0000 Subject: [PATCH 335/443] Bump actions/checkout from 3 to 4 (#1386) --- .github/workflows/gh-pages.yml | 2 +- .github/workflows/test.yml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 9ef038b23c1..e106f4b5b16 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@nightly diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e20b0675c1..8d614b0f6d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: name: Check doc runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@nightly - run: cargo install cargo-deadlinks @@ -58,7 +58,7 @@ jobs: variant: minimal_versions steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: MSRV if: ${{ matrix.variant == 'MSRV' }} run: cp Cargo.lock.msrv Cargo.lock @@ -117,7 +117,7 @@ jobs: toolchain: stable steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@master with: @@ -143,7 +143,7 @@ jobs: test-miri: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install toolchain run: | rustup toolchain install nightly --component miri @@ -163,7 +163,7 @@ jobs: test-no-std: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@nightly with: @@ -175,7 +175,7 @@ jobs: # test-avr: # runs-on: ubuntu-latest # steps: - # - uses: actions/checkout@v3 + # - uses: actions/checkout@v4 # - name: Install toolchain # uses: dtolnay/rust-toolchain@nightly # with: @@ -186,7 +186,7 @@ jobs: test-ios: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install toolchain uses: dtolnay/rust-toolchain@nightly with: From e5a366d07fc9d07d08ce733f9893e6bbbf1381a0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 13 Feb 2024 11:13:57 +0000 Subject: [PATCH 336/443] Update zerocopy; trim unused methods (#1393) * Remove mention of stdsimd * Move fns FloatSIMDUtils::replace, extract to new cfg-gated trait FloatSIMDScalarUtils * Remove unused utility methods on floats, bool * Remove unused import * Update to zerocopy 0.8.0-alpha.5 * Remove unneeded import of Float --- Cargo.toml | 2 +- rand_core/Cargo.toml | 2 +- rand_core/src/impls.rs | 4 +-- rand_distr/tests/pdf.rs | 2 +- src/distributions/uniform.rs | 5 +-- src/distributions/utils.rs | 59 +++++++++++------------------------- src/lib.rs | 2 +- 7 files changed, 24 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index da14228a547..b2c7f3d6162 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ rand_core = { path = "rand_core", version = "0.7.0", default-features = false } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "0.4.0", default-features = false, optional = true } -zerocopy = { version = "0.7.20", default-features = false, features = ["simd"] } +zerocopy = { version = "=0.8.0-alpha.5", default-features = false, features = ["simd"] } [target.'cfg(unix)'.dependencies] # Used for fork protection (reseeding.rs) diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 8c9d902a70c..b53a01634b5 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -32,4 +32,4 @@ serde1 = ["serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } getrandom = { version = "0.2", optional = true } -zerocopy = { version = "0.7.20", default-features = false } +zerocopy = { version = "=0.8.0-alpha.5", default-features = false } diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index d7dcd3457d3..a8fc1a7e8c6 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -19,7 +19,7 @@ use crate::RngCore; use core::cmp::min; -use zerocopy::AsBytes; +use zerocopy::{IntoBytes, NoCell}; /// Implement `next_u64` via `next_u32`, little-endian order. pub fn next_u64_via_u32(rng: &mut R) -> u64 { @@ -53,7 +53,7 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } } -trait Observable: AsBytes + Copy { +trait Observable: IntoBytes + NoCell + Copy { fn to_le(self) -> Self; } impl Observable for u32 { diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 14db18153a4..b4fd7810926 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -9,7 +9,7 @@ #![allow(clippy::float_cmp)] use average::Histogram; -use rand::{Rng, SeedableRng}; +use rand::Rng; use rand_distr::{Normal, SkewNormal}; const HIST_LEN: usize = 100; diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 879d8ea2582..5e6b6ae3f9e 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -115,10 +115,6 @@ use crate::distributions::Distribution; use crate::distributions::Standard; use crate::{Rng, RngCore}; -#[cfg(not(feature = "std"))] -#[allow(unused_imports)] // rustc doesn't detect that this is actually used -use crate::distributions::utils::Float; - #[cfg(feature = "simd_support")] use core::simd::prelude::*; #[cfg(feature = "simd_support")] use core::simd::{LaneCount, SupportedLaneCount}; @@ -1250,6 +1246,7 @@ impl UniformSampler for UniformDuration { mod tests { use super::*; use crate::rngs::mock::StepRng; + use crate::distributions::utils::FloatSIMDScalarUtils; #[test] #[cfg(feature = "serde1")] diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index f3b3089d7e1..e3ef5bcdb8b 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -228,22 +228,16 @@ pub(crate) trait FloatSIMDUtils { // value, not by retaining the binary representation. type UInt; fn cast_from_int(i: Self::UInt) -> Self; +} +#[cfg(test)] +pub(crate) trait FloatSIMDScalarUtils: FloatSIMDUtils { type Scalar; + fn replace(self, index: usize, new_value: Self::Scalar) -> Self; fn extract(self, index: usize) -> Self::Scalar; } -/// Implement functions available in std builds but missing from core primitives -#[cfg(not(std))] -// False positive: We are following `std` here. -#[allow(clippy::wrong_self_convention)] -pub(crate) trait Float: Sized { - fn is_nan(self) -> bool; - fn is_infinite(self) -> bool; - fn is_finite(self) -> bool; -} - /// Implement functions on f32/f64 to give them APIs similar to SIMD types pub(crate) trait FloatAsSIMD: Sized { const LEN: usize = 1; @@ -265,8 +259,6 @@ impl IntAsSIMD for u64 {} pub(crate) trait BoolAsSIMD: Sized { fn any(self) -> bool; - fn all(self) -> bool; - fn none(self) -> bool; } impl BoolAsSIMD for bool { @@ -274,41 +266,12 @@ impl BoolAsSIMD for bool { fn any(self) -> bool { self } - - #[inline(always)] - fn all(self) -> bool { - self - } - - #[inline(always)] - fn none(self) -> bool { - !self - } } macro_rules! scalar_float_impl { ($ty:ident, $uty:ident) => { - #[cfg(not(std))] - impl Float for $ty { - #[inline] - fn is_nan(self) -> bool { - self != self - } - - #[inline] - fn is_infinite(self) -> bool { - self == ::core::$ty::INFINITY || self == ::core::$ty::NEG_INFINITY - } - - #[inline] - fn is_finite(self) -> bool { - !(self.is_nan() || self.is_infinite()) - } - } - impl FloatSIMDUtils for $ty { type Mask = bool; - type Scalar = $ty; type UInt = $uty; #[inline(always)] @@ -351,6 +314,11 @@ macro_rules! scalar_float_impl { fn cast_from_int(i: Self::UInt) -> Self { i as $ty } + } + + #[cfg(test)] + impl FloatSIMDScalarUtils for $ty { + type Scalar = $ty; #[inline] fn replace(self, index: usize, new_value: Self::Scalar) -> Self { @@ -380,7 +348,6 @@ macro_rules! simd_impl { where LaneCount: SupportedLaneCount { type Mask = Mask<<$fty as SimdElement>::Mask, LANES>; - type Scalar = $fty; type UInt = Simd<$uty, LANES>; #[inline(always)] @@ -429,6 +396,14 @@ macro_rules! simd_impl { fn cast_from_int(i: Self::UInt) -> Self { i.cast() } + } + + #[cfg(test)] + impl FloatSIMDScalarUtils for Simd<$fty, LANES> + where + LaneCount: SupportedLaneCount, + { + type Scalar = $fty; #[inline] fn replace(mut self, index: usize, new_value: Self::Scalar) -> Self { diff --git a/src/lib.rs b/src/lib.rs index 8ade2881d5d..dc9e29d6277 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,7 @@ #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] -#![cfg_attr(feature = "simd_support", feature(stdsimd, portable_simd))] +#![cfg_attr(feature = "simd_support", feature(portable_simd))] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![allow( clippy::float_cmp, From 97556c4c76bc9efad1b4905e98e104293f51b260 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:15:41 +0000 Subject: [PATCH 337/443] Bump actions/cache from 3 to 4 (#1385) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d614b0f6d4..9a4e860aec0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -124,7 +124,7 @@ jobs: target: ${{ matrix.target }} toolchain: ${{ matrix.toolchain }} - name: Cache cargo plugins - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cargo/bin/ key: ${{ runner.os }}-cargo-plugins From ef245fd5823a677ed6b40f91cd693dd403877286 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 11:33:39 +0000 Subject: [PATCH 338/443] Update special requirement from 0.8.1 to 0.10.3 (#1384) --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 9d3baee0a88..8339580a504 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -39,4 +39,4 @@ rand = { path = "..", version = "0.9.0", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.13", features = [ "std" ] } # Special functions for testing distributions -special = "0.8.1" +special = "0.10.3" From dba696e9f171b18129517fed3b05d01a52b031f9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 15 Feb 2024 08:17:45 +0000 Subject: [PATCH 339/443] =?UTF-8?q?Rename=20WeightedError=20=E2=86=92=20We?= =?UTF-8?q?ightError;=20add=20IndexedRandom,=20IndexedMutRandom=20(#1382)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove deprecated module rand::distributions::weighted * WeightedTree: return InvalidWeight on not-a-number * WeightedTree::try_sample return AllWeightsZero given no weights * Rename WeightedError -> WeightError and revise variants * Re-export WeightError from rand::seq * Revise errors of rand::index::sample_weighted * Split SliceRandom into IndexedRandom, IndexedMutRandom and SliceRandom --- examples/monty-hall.rs | 2 +- rand_distr/src/lib.rs | 2 +- rand_distr/src/weighted_alias.rs | 49 ++-- rand_distr/src/weighted_tree.rs | 63 +++-- src/distributions/mod.rs | 9 +- src/distributions/slice.rs | 14 +- src/distributions/weighted.rs | 47 ---- src/distributions/weighted_index.rs | 99 +++---- src/prelude.rs | 3 +- src/seq/index.rs | 42 +-- src/seq/mod.rs | 395 +++++++++++++--------------- 11 files changed, 329 insertions(+), 396 deletions(-) delete mode 100644 src/distributions/weighted.rs diff --git a/examples/monty-hall.rs b/examples/monty-hall.rs index 23e11178969..7499193bcea 100644 --- a/examples/monty-hall.rs +++ b/examples/monty-hall.rs @@ -61,7 +61,7 @@ fn simulate(random_door: &Uniform, rng: &mut R) -> SimulationResult // Returns the door the game host opens given our choice and knowledge of // where the car is. The game host will never open the door with the car. fn game_host_open(car: u32, choice: u32, rng: &mut R) -> u32 { - use rand::seq::SliceRandom; + use rand::seq::IndexedRandom; *free_doors(&[car, choice]).choose(rng).unwrap() } diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 1e28aaaa79a..dc155bb5d5d 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -130,7 +130,7 @@ pub use self::weibull::{Error as WeibullError, Weibull}; pub use self::zipf::{Zeta, ZetaError, Zipf, ZipfError}; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -pub use rand::distributions::{WeightedError, WeightedIndex}; +pub use rand::distributions::{WeightError, WeightedIndex}; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use weighted_alias::WeightedAliasIndex; diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 170de80c4a5..236e2ad734b 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -9,7 +9,7 @@ //! This module contains an implementation of alias method for sampling random //! indices with probabilities proportional to a collection of weights. -use super::WeightedError; +use super::WeightError; use crate::{uniform::SampleUniform, Distribution, Uniform}; use core::fmt; use core::iter::Sum; @@ -79,18 +79,15 @@ pub struct WeightedAliasIndex { impl WeightedAliasIndex { /// Creates a new [`WeightedAliasIndex`]. /// - /// Returns an error if: - /// - The vector is empty. - /// - The vector is longer than `u32::MAX`. - /// - For any weight `w`: `w < 0` or `w > max` where `max = W::MAX / - /// weights.len()`. - /// - The sum of weights is zero. - pub fn new(weights: Vec) -> Result { + /// Error cases: + /// - [`WeightError::InvalidInput`] when `weights.len()` is zero or greater than `u32::MAX`. + /// - [`WeightError::InvalidWeight`] when a weight is not-a-number, + /// negative or greater than `max = W::MAX / weights.len()`. + /// - [`WeightError::InsufficientNonZero`] when the sum of all weights is zero. + pub fn new(weights: Vec) -> Result { let n = weights.len(); - if n == 0 { - return Err(WeightedError::NoItem); - } else if n > ::core::u32::MAX as usize { - return Err(WeightedError::TooMany); + if n == 0 || n > ::core::u32::MAX as usize { + return Err(WeightError::InvalidInput); } let n = n as u32; @@ -101,7 +98,7 @@ impl WeightedAliasIndex { .iter() .all(|&w| W::ZERO <= w && w <= max_weight_size) { - return Err(WeightedError::InvalidWeight); + return Err(WeightError::InvalidWeight); } // The sum of weights will represent 100% of no alias odds. @@ -113,7 +110,7 @@ impl WeightedAliasIndex { weight_sum }; if weight_sum == W::ZERO { - return Err(WeightedError::AllWeightsZero); + return Err(WeightError::InsufficientNonZero); } // `weight_sum` would have been zero if `try_from_lossy` causes an error here. @@ -382,23 +379,23 @@ mod test { // Floating point special cases assert_eq!( WeightedAliasIndex::new(vec![::core::f32::INFINITY]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![-0_f32]).unwrap_err(), - WeightedError::AllWeightsZero + WeightError::InsufficientNonZero ); assert_eq!( WeightedAliasIndex::new(vec![-1_f32]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![-::core::f32::INFINITY]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![::core::f32::NAN]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); } @@ -416,11 +413,11 @@ mod test { // Signed integer special cases assert_eq!( WeightedAliasIndex::new(vec![-1_i128]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![::core::i128::MIN]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); } @@ -438,11 +435,11 @@ mod test { // Signed integer special cases assert_eq!( WeightedAliasIndex::new(vec![-1_i8]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![::core::i8::MIN]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); } @@ -486,15 +483,15 @@ mod test { assert_eq!( WeightedAliasIndex::::new(vec![]).unwrap_err(), - WeightedError::NoItem + WeightError::InvalidInput ); assert_eq!( WeightedAliasIndex::new(vec![W::ZERO]).unwrap_err(), - WeightedError::AllWeightsZero + WeightError::InsufficientNonZero ); assert_eq!( WeightedAliasIndex::new(vec![W::MAX, W::MAX]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); } diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index b308cdb2c04..d5b4ef467d8 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -11,7 +11,7 @@ use core::ops::SubAssign; -use super::WeightedError; +use super::WeightError; use crate::Distribution; use alloc::vec::Vec; use rand::distributions::uniform::{SampleBorrow, SampleUniform}; @@ -98,15 +98,19 @@ impl + Weight> WeightedTreeIndex { /// Creates a new [`WeightedTreeIndex`] from a slice of weights. - pub fn new(weights: I) -> Result + /// + /// Error cases: + /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`WeightError::Overflow`] when the sum of all weights overflows. + pub fn new(weights: I) -> Result where I: IntoIterator, I::Item: SampleBorrow, { let mut subtotals: Vec = weights.into_iter().map(|x| x.borrow().clone()).collect(); for weight in subtotals.iter() { - if *weight < W::ZERO { - return Err(WeightedError::InvalidWeight); + if !(*weight >= W::ZERO) { + return Err(WeightError::InvalidWeight); } } let n = subtotals.len(); @@ -115,7 +119,7 @@ impl + Weight> let parent = (i - 1) / 2; subtotals[parent] .checked_add_assign(&w) - .map_err(|()| WeightedError::Overflow)?; + .map_err(|()| WeightError::Overflow)?; } Ok(Self { subtotals }) } @@ -164,14 +168,18 @@ impl + Weight> } /// Appends a new weight at the end. - pub fn push(&mut self, weight: W) -> Result<(), WeightedError> { - if weight < W::ZERO { - return Err(WeightedError::InvalidWeight); + /// + /// Error cases: + /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`WeightError::Overflow`] when the sum of all weights overflows. + pub fn push(&mut self, weight: W) -> Result<(), WeightError> { + if !(weight >= W::ZERO) { + return Err(WeightError::InvalidWeight); } if let Some(total) = self.subtotals.first() { let mut total = total.clone(); if total.checked_add_assign(&weight).is_err() { - return Err(WeightedError::Overflow); + return Err(WeightError::Overflow); } } let mut index = self.len(); @@ -184,9 +192,13 @@ impl + Weight> } /// Updates the weight at an index. - pub fn update(&mut self, mut index: usize, weight: W) -> Result<(), WeightedError> { - if weight < W::ZERO { - return Err(WeightedError::InvalidWeight); + /// + /// Error cases: + /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`WeightError::Overflow`] when the sum of all weights overflows. + pub fn update(&mut self, mut index: usize, weight: W) -> Result<(), WeightError> { + if !(weight >= W::ZERO) { + return Err(WeightError::InvalidWeight); } let old_weight = self.get(index); if weight > old_weight { @@ -195,7 +207,7 @@ impl + Weight> if let Some(total) = self.subtotals.first() { let mut total = total.clone(); if total.checked_add_assign(&difference).is_err() { - return Err(WeightedError::Overflow); + return Err(WeightError::Overflow); } } self.subtotals[index] @@ -235,13 +247,10 @@ impl + Weight> /// /// Returns an error if there are no elements or all weights are zero. This /// is unlike [`Distribution::sample`], which panics in those cases. - fn try_sample(&self, rng: &mut R) -> Result { - if self.subtotals.is_empty() { - return Err(WeightedError::NoItem); - } - let total_weight = self.subtotals[0].clone(); + fn try_sample(&self, rng: &mut R) -> Result { + let total_weight = self.subtotals.first().cloned().unwrap_or(W::ZERO); if total_weight == W::ZERO { - return Err(WeightedError::AllWeightsZero); + return Err(WeightError::InsufficientNonZero); } let mut target_weight = rng.gen_range(W::ZERO..total_weight); let mut index = 0; @@ -296,7 +305,7 @@ mod test { let tree = WeightedTreeIndex::::new(&[]).unwrap(); assert_eq!( tree.try_sample(&mut rng).unwrap_err(), - WeightedError::NoItem + WeightError::InsufficientNonZero ); } @@ -304,11 +313,11 @@ mod test { fn test_overflow_error() { assert_eq!( WeightedTreeIndex::new(&[i32::MAX, 2]), - Err(WeightedError::Overflow) + Err(WeightError::Overflow) ); let mut tree = WeightedTreeIndex::new(&[i32::MAX - 2, 1]).unwrap(); - assert_eq!(tree.push(3), Err(WeightedError::Overflow)); - assert_eq!(tree.update(1, 4), Err(WeightedError::Overflow)); + assert_eq!(tree.push(3), Err(WeightError::Overflow)); + assert_eq!(tree.update(1, 4), Err(WeightError::Overflow)); tree.update(1, 2).unwrap(); } @@ -318,7 +327,7 @@ mod test { let mut rng = crate::test::rng(0x9c9fa0b0580a7031); assert_eq!( tree.try_sample(&mut rng).unwrap_err(), - WeightedError::AllWeightsZero + WeightError::InsufficientNonZero ); } @@ -326,14 +335,14 @@ mod test { fn test_invalid_weight_error() { assert_eq!( WeightedTreeIndex::::new(&[1, -1]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); let mut tree = WeightedTreeIndex::::new(&[]).unwrap(); - assert_eq!(tree.push(-1).unwrap_err(), WeightedError::InvalidWeight); + assert_eq!(tree.push(-1).unwrap_err(), WeightError::InvalidWeight); tree.push(1).unwrap(); assert_eq!( tree.update(0, -1).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); } diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 5adb82f8111..39d967d4f60 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -108,13 +108,6 @@ pub mod hidden_export { pub use super::float::IntoFloat; // used by rand_distr } pub mod uniform; -#[deprecated( - since = "0.8.0", - note = "use rand::distributions::{WeightedIndex, WeightedError} instead" -)] -#[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -pub mod weighted; pub use self::bernoulli::{Bernoulli, BernoulliError}; pub use self::distribution::{Distribution, DistIter, DistMap}; @@ -126,7 +119,7 @@ pub use self::slice::Slice; #[doc(inline)] pub use self::uniform::Uniform; #[cfg(feature = "alloc")] -pub use self::weighted_index::{Weight, WeightedError, WeightedIndex}; +pub use self::weighted_index::{Weight, WeightError, WeightedIndex}; #[allow(unused)] use crate::Rng; diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 224bf1712c1..5fc08751f6c 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -15,7 +15,7 @@ use alloc::string::String; /// [`Slice::new`] constructs a distribution referencing a slice and uniformly /// samples references from the items in the slice. It may do extra work up /// front to make sampling of multiple values faster; if only one sample from -/// the slice is required, [`SliceRandom::choose`] can be more efficient. +/// the slice is required, [`IndexedRandom::choose`] can be more efficient. /// /// Steps are taken to avoid bias which might be present in naive /// implementations; for example `slice[rng.gen() % slice.len()]` samples from @@ -25,7 +25,7 @@ use alloc::string::String; /// This distribution samples with replacement; each sample is independent. /// Sampling without replacement requires state to be retained, and therefore /// cannot be handled by a distribution; you should instead consider methods -/// on [`SliceRandom`], such as [`SliceRandom::choose_multiple`]. +/// on [`IndexedRandom`], such as [`IndexedRandom::choose_multiple`]. /// /// # Example /// @@ -48,11 +48,11 @@ use alloc::string::String; /// assert!(vowel_string.chars().all(|c| vowels.contains(&c))); /// ``` /// -/// For a single sample, [`SliceRandom::choose`][crate::seq::SliceRandom::choose] +/// For a single sample, [`IndexedRandom::choose`][crate::seq::IndexedRandom::choose] /// may be preferred: /// /// ``` -/// use rand::seq::SliceRandom; +/// use rand::seq::IndexedRandom; /// /// let vowels = ['a', 'e', 'i', 'o', 'u']; /// let mut rng = rand::thread_rng(); @@ -60,9 +60,9 @@ use alloc::string::String; /// println!("{}", vowels.choose(&mut rng).unwrap()) /// ``` /// -/// [`SliceRandom`]: crate::seq::SliceRandom -/// [`SliceRandom::choose`]: crate::seq::SliceRandom::choose -/// [`SliceRandom::choose_multiple`]: crate::seq::SliceRandom::choose_multiple +/// [`IndexedRandom`]: crate::seq::IndexedRandom +/// [`IndexedRandom::choose`]: crate::seq::IndexedRandom::choose +/// [`IndexedRandom::choose_multiple`]: crate::seq::IndexedRandom::choose_multiple #[derive(Debug, Clone, Copy)] pub struct Slice<'a, T> { slice: &'a [T], diff --git a/src/distributions/weighted.rs b/src/distributions/weighted.rs deleted file mode 100644 index 846b9df9c28..00000000000 --- a/src/distributions/weighted.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Weighted index sampling -//! -//! This module is deprecated. Use [`crate::distributions::WeightedIndex`] and -//! [`crate::distributions::WeightedError`] instead. - -pub use super::{WeightedIndex, WeightedError}; - -#[allow(missing_docs)] -#[deprecated(since = "0.8.0", note = "moved to rand_distr crate")] -pub mod alias_method { - // This module exists to provide a deprecation warning which minimises - // compile errors, but still fails to compile if ever used. - use core::marker::PhantomData; - use alloc::vec::Vec; - use super::WeightedError; - - #[derive(Debug)] - pub struct WeightedIndex { - _phantom: PhantomData, - } - impl WeightedIndex { - pub fn new(_weights: Vec) -> Result { - Err(WeightedError::NoItem) - } - } - - pub trait Weight {} - macro_rules! impl_weight { - () => {}; - ($T:ident, $($more:ident,)*) => { - impl Weight for $T {} - impl_weight!($($more,)*); - }; - } - impl_weight!(f64, f32,); - impl_weight!(u8, u16, u32, u64, usize,); - impl_weight!(i8, i16, i32, i64, isize,); - impl_weight!(u128, i128,); -} diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 0b1b4da947c..49cb02d6ade 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -94,22 +94,25 @@ impl WeightedIndex { /// in `weights`. The weights can use any type `X` for which an /// implementation of [`Uniform`] exists. /// - /// Returns an error if the iterator is empty, if any weight is `< 0`, or - /// if its total value is 0. + /// Error cases: + /// - [`WeightError::InvalidInput`] when the iterator `weights` is empty. + /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`WeightError::InsufficientNonZero`] when the sum of all weights is zero. + /// - [`WeightError::Overflow`] when the sum of all weights overflows. /// /// [`Uniform`]: crate::distributions::uniform::Uniform - pub fn new(weights: I) -> Result, WeightedError> + pub fn new(weights: I) -> Result, WeightError> where I: IntoIterator, I::Item: SampleBorrow, X: Weight, { let mut iter = weights.into_iter(); - let mut total_weight: X = iter.next().ok_or(WeightedError::NoItem)?.borrow().clone(); + let mut total_weight: X = iter.next().ok_or(WeightError::InvalidInput)?.borrow().clone(); let zero = X::ZERO; if !(total_weight >= zero) { - return Err(WeightedError::InvalidWeight); + return Err(WeightError::InvalidWeight); } let mut weights = Vec::::with_capacity(iter.size_hint().0); @@ -117,17 +120,17 @@ impl WeightedIndex { // Note that `!(w >= x)` is not equivalent to `w < x` for partially // ordered types due to NaNs which are equal to nothing. if !(w.borrow() >= &zero) { - return Err(WeightedError::InvalidWeight); + return Err(WeightError::InvalidWeight); } weights.push(total_weight.clone()); if let Err(()) = total_weight.checked_add_assign(w.borrow()) { - return Err(WeightedError::Overflow); + return Err(WeightError::Overflow); } } if total_weight == zero { - return Err(WeightedError::AllWeightsZero); + return Err(WeightError::InsufficientNonZero); } let distr = X::Sampler::new(zero, total_weight.clone()).unwrap(); @@ -146,16 +149,19 @@ impl WeightedIndex { /// weights is modified. No allocations are performed, unless the weight type `X` uses /// allocation internally. /// - /// In case of error, `self` is not modified. + /// In case of error, `self` is not modified. Error cases: + /// - [`WeightError::InvalidInput`] when `new_weights` are not ordered by + /// index or an index is too large. + /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`WeightError::InsufficientNonZero`] when the sum of all weights is zero. + /// Note that due to floating-point loss of precision, this case is not + /// always correctly detected; usage of a fixed-point weight type may be + /// preferred. /// /// Updates take `O(N)` time. If you need to frequently update weights, consider /// [`rand_distr::weighted_tree`](https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html) /// as an alternative where an update is `O(log N)`. - /// - /// Note: Updating floating-point weights may cause slight inaccuracies in the total weight. - /// This method may not return `WeightedError::AllWeightsZero` when all weights - /// are zero if using floating-point weights. - pub fn update_weights(&mut self, new_weights: &[(usize, &X)]) -> Result<(), WeightedError> + pub fn update_weights(&mut self, new_weights: &[(usize, &X)]) -> Result<(), WeightError> where X: for<'a> ::core::ops::AddAssign<&'a X> + for<'a> ::core::ops::SubAssign<&'a X> @@ -176,14 +182,14 @@ impl WeightedIndex { for &(i, w) in new_weights { if let Some(old_i) = prev_i { if old_i >= i { - return Err(WeightedError::InvalidWeight); + return Err(WeightError::InvalidInput); } } if !(*w >= zero) { - return Err(WeightedError::InvalidWeight); + return Err(WeightError::InvalidWeight); } if i > self.cumulative_weights.len() { - return Err(WeightedError::TooMany); + return Err(WeightError::InvalidInput); } let mut old_w = if i < self.cumulative_weights.len() { @@ -200,7 +206,7 @@ impl WeightedIndex { prev_i = Some(i); } if total_weight <= zero { - return Err(WeightedError::AllWeightsZero); + return Err(WeightError::InsufficientNonZero); } // Update the weights. Because we checked all the preconditions in the @@ -328,15 +334,15 @@ mod test { fn test_accepting_nan() { assert_eq!( WeightedIndex::new(&[core::f32::NAN, 0.5]).unwrap_err(), - WeightedError::InvalidWeight, + WeightError::InvalidWeight, ); assert_eq!( WeightedIndex::new(&[core::f32::NAN]).unwrap_err(), - WeightedError::InvalidWeight, + WeightError::InvalidWeight, ); assert_eq!( WeightedIndex::new(&[0.5, core::f32::NAN]).unwrap_err(), - WeightedError::InvalidWeight, + WeightError::InvalidWeight, ); assert_eq!( @@ -344,7 +350,7 @@ mod test { .unwrap() .update_weights(&[(0, &core::f32::NAN)]) .unwrap_err(), - WeightedError::InvalidWeight, + WeightError::InvalidWeight, ) } @@ -404,23 +410,23 @@ mod test { assert_eq!( WeightedIndex::new(&[10][0..0]).unwrap_err(), - WeightedError::NoItem + WeightError::InvalidInput ); assert_eq!( WeightedIndex::new(&[0]).unwrap_err(), - WeightedError::AllWeightsZero + WeightError::InsufficientNonZero ); assert_eq!( WeightedIndex::new(&[10, 20, -1, 30]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); assert_eq!( WeightedIndex::new(&[-10, 20, 1, 30]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); assert_eq!( WeightedIndex::new(&[-10]).unwrap_err(), - WeightedError::InvalidWeight + WeightError::InvalidWeight ); } @@ -497,43 +503,42 @@ mod test { fn overflow() { assert_eq!( WeightedIndex::new([2, usize::MAX]), - Err(WeightedError::Overflow) + Err(WeightError::Overflow) ); } } -/// Error type returned from `WeightedIndex::new`. +/// Errors returned by weighted distributions #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum WeightedError { - /// The provided weight collection contains no items. - NoItem, +pub enum WeightError { + /// The input weight sequence is empty, too long, or wrongly ordered + InvalidInput, - /// A weight is either less than zero, greater than the supported maximum, - /// NaN, or otherwise invalid. + /// A weight is negative, too large for the distribution, or not a valid number InvalidWeight, - /// All items in the provided weight collection are zero. - AllWeightsZero, - - /// Too many weights are provided (length greater than `u32::MAX`) - TooMany, + /// Not enough non-zero weights are available to sample values + /// + /// When attempting to sample a single value this implies that all weights + /// are zero. When attempting to sample `amount` values this implies that + /// less than `amount` weights are greater than zero. + InsufficientNonZero, - /// The sum of weights overflows + /// Overflow when calculating the sum of weights Overflow, } #[cfg(feature = "std")] -impl std::error::Error for WeightedError {} +impl std::error::Error for WeightError {} -impl fmt::Display for WeightedError { +impl fmt::Display for WeightError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { - WeightedError::NoItem => "No weights provided in distribution", - WeightedError::InvalidWeight => "A weight is invalid in distribution", - WeightedError::AllWeightsZero => "All weights are zero in distribution", - WeightedError::TooMany => "Too many weights (hit u32::MAX) in distribution", - WeightedError::Overflow => "The sum of weights overflowed", + WeightError::InvalidInput => "Weights sequence is empty/too long/unordered", + WeightError::InvalidWeight => "A weight is negative, too large or not a valid number", + WeightError::InsufficientNonZero => "Not enough weights > zero", + WeightError::Overflow => "Overflow when summing weights", }) } } diff --git a/src/prelude.rs b/src/prelude.rs index 1ce747b6255..35fee3d73fd 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -27,7 +27,8 @@ pub use crate::rngs::SmallRng; #[doc(no_inline)] #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use crate::rngs::ThreadRng; -#[doc(no_inline)] pub use crate::seq::{IteratorRandom, SliceRandom}; +#[doc(no_inline)] +pub use crate::seq::{IndexedMutRandom, IndexedRandom, IteratorRandom, SliceRandom}; #[doc(no_inline)] #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use crate::{random, thread_rng}; diff --git a/src/seq/index.rs b/src/seq/index.rs index 956ea60ed81..e98b7ec1061 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -17,7 +17,7 @@ use alloc::collections::BTreeSet; #[cfg(feature = "std")] use std::collections::HashSet; #[cfg(feature = "std")] -use crate::distributions::WeightedError; +use super::WeightError; #[cfg(feature = "alloc")] use crate::{Rng, distributions::{uniform::SampleUniform, Distribution, Uniform}}; @@ -267,14 +267,16 @@ where R: Rng + ?Sized { /// sometimes be useful to have the indices themselves so this is provided as /// an alternative. /// -/// This implementation uses `O(length + amount)` space and `O(length)` time. +/// Error cases: +/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. +/// - [`WeightError::InsufficientNonZero`] when fewer than `amount` weights are positive. /// -/// Panics if `amount > length`. +/// This implementation uses `O(length + amount)` space and `O(length)` time. #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn sample_weighted( rng: &mut R, length: usize, weight: F, amount: usize, -) -> Result +) -> Result where R: Rng + ?Sized, F: Fn(usize) -> X, @@ -300,11 +302,13 @@ where /// in this paper: https://doi.org/10.1016/j.ipl.2005.11.003 /// It uses `O(length + amount)` space and `O(length)` time. /// -/// Panics if `amount > length`. +/// Error cases: +/// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. +/// - [`WeightError::InsufficientNonZero`] when fewer than `amount` weights are positive. #[cfg(feature = "std")] fn sample_efraimidis_spirakis( rng: &mut R, length: N, weight: F, amount: N, -) -> Result +) -> Result where R: Rng + ?Sized, F: Fn(usize) -> X, @@ -316,10 +320,6 @@ where return Ok(IndexVec::U32(Vec::new())); } - if amount > length { - panic!("`amount` of samples must be less than or equal to `length`"); - } - struct Element { index: N, key: f64, @@ -347,22 +347,27 @@ where let mut index = N::zero(); while index < length { let weight = weight(index.as_usize()).into(); - if !(weight >= 0.) { - return Err(WeightedError::InvalidWeight); + if weight > 0.0 { + let key = rng.gen::().powf(1.0 / weight); + candidates.push(Element { index, key }); + } else if !(weight >= 0.0) { + return Err(WeightError::InvalidWeight); } - let key = rng.gen::().powf(1.0 / weight); - candidates.push(Element { index, key }); - index += N::one(); } + let avail = candidates.len(); + if avail < amount.as_usize() { + return Err(WeightError::InsufficientNonZero); + } + // Partially sort the array to find the `amount` elements with the greatest // keys. Do this by using `select_nth_unstable` to put the elements with // the *smallest* keys at the beginning of the list in `O(n)` time, which // provides equivalent information about the elements with the *greatest* keys. let (_, mid, greater) - = candidates.select_nth_unstable(length.as_usize() - amount.as_usize()); + = candidates.select_nth_unstable(avail - amount.as_usize()); let mut result: Vec = Vec::with_capacity(amount.as_usize()); result.push(mid.index); @@ -576,7 +581,7 @@ mod test { #[test] fn test_sample_weighted() { let seed_rng = crate::test::rng; - for &(amount, len) in &[(0, 10), (5, 10), (10, 10)] { + for &(amount, len) in &[(0, 10), (5, 10), (9, 10)] { let v = sample_weighted(&mut seed_rng(423), len, |i| i as f64, amount).unwrap(); match v { IndexVec::U32(mut indices) => { @@ -591,6 +596,9 @@ mod test { IndexVec::USize(_) => panic!("expected `IndexVec::U32`"), } } + + let r = sample_weighted(&mut seed_rng(423), 10, |i| i as f64, 10); + assert_eq!(r.unwrap_err(), WeightError::InsufficientNonZero); } #[test] diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 9012b21b909..f5cbc6008e9 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -10,8 +10,10 @@ //! //! This module provides: //! -//! * [`SliceRandom`] slice sampling and mutation -//! * [`IteratorRandom`] iterator sampling +//! * [`IndexedRandom`] for sampling slices and other indexable lists +//! * [`IndexedMutRandom`] for sampling slices and other mutably indexable lists +//! * [`SliceRandom`] for mutating slices +//! * [`IteratorRandom`] for sampling iterators //! * [`index::sample`] low-level API to choose multiple indices from //! `0..length` //! @@ -32,41 +34,36 @@ pub mod index; mod increasing_uniform; #[cfg(feature = "alloc")] -use core::ops::Index; +#[doc(no_inline)] +pub use crate::distributions::WeightError; + +use core::ops::{Index, IndexMut}; #[cfg(feature = "alloc")] use alloc::vec::Vec; #[cfg(feature = "alloc")] use crate::distributions::uniform::{SampleBorrow, SampleUniform}; -#[cfg(feature = "alloc")] -use crate::distributions::{Weight, WeightedError}; +#[cfg(feature = "alloc")] use crate::distributions::Weight; use crate::Rng; use self::coin_flipper::CoinFlipper; use self::increasing_uniform::IncreasingUniform; -/// Extension trait on slices, providing random mutation and sampling methods. -/// -/// This trait is implemented on all `[T]` slice types, providing several -/// methods for choosing and shuffling elements. You must `use` this trait: -/// -/// ``` -/// use rand::seq::SliceRandom; +/// Extension trait on indexable lists, providing random sampling methods. /// -/// let mut rng = rand::thread_rng(); -/// let mut bytes = "Hello, random!".to_string().into_bytes(); -/// bytes.shuffle(&mut rng); -/// let str = String::from_utf8(bytes).unwrap(); -/// println!("{}", str); -/// ``` -/// Example output (non-deterministic): -/// ```none -/// l,nmroHado !le -/// ``` -pub trait SliceRandom { - /// The element type. - type Item; +/// This trait is implemented on `[T]` slice types. Other types supporting +/// [`std::ops::Index`] may implement this (only [`Self::len`] must be +/// specified). +pub trait IndexedRandom: Index { + /// The length + fn len(&self) -> usize; + + /// True when the length is zero + #[inline] + fn is_empty(&self) -> bool { + self.len() == 0 + } /// Uniformly sample one element /// @@ -79,26 +76,23 @@ pub trait SliceRandom { /// /// ``` /// use rand::thread_rng; - /// use rand::seq::SliceRandom; + /// use rand::seq::IndexedRandom; /// /// let choices = [1, 2, 4, 8, 16, 32]; /// let mut rng = thread_rng(); /// println!("{:?}", choices.choose(&mut rng)); /// assert_eq!(choices[..0].choose(&mut rng), None); /// ``` - fn choose(&self, rng: &mut R) -> Option<&Self::Item> - where - R: Rng + ?Sized; - - /// Uniformly sample one element (mut) - /// - /// Returns a mutable reference to one uniformly-sampled random element of - /// the slice, or `None` if the slice is empty. - /// - /// For slices, complexity is `O(1)`. - fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> + fn choose(&self, rng: &mut R) -> Option<&Self::Output> where - R: Rng + ?Sized; + R: Rng + ?Sized, + { + if self.is_empty() { + None + } else { + Some(&self[gen_index(rng, self.len())]) + } + } /// Uniformly sample `amount` distinct elements /// @@ -112,7 +106,7 @@ pub trait SliceRandom { /// /// # Example /// ``` - /// use rand::seq::SliceRandom; + /// use rand::seq::IndexedRandom; /// /// let mut rng = &mut rand::thread_rng(); /// let sample = "Hello, audience!".as_bytes(); @@ -128,9 +122,18 @@ pub trait SliceRandom { /// ``` #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] - fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter + fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter where - R: Rng + ?Sized; + Self::Output: Sized, + R: Rng + ?Sized, + { + let amount = ::core::cmp::min(amount, self.len()); + SliceChooseIter { + slice: self, + _phantom: Default::default(), + indices: index::sample(rng, self.len(), amount).into_iter(), + } + } /// Biased sampling for one element /// @@ -158,48 +161,24 @@ pub trait SliceRandom { /// // and 'd' will never be printed /// println!("{:?}", choices.choose_weighted(&mut rng, |item| item.1).unwrap().0); /// ``` - /// [`choose`]: SliceRandom::choose - /// [`choose_weighted_mut`]: SliceRandom::choose_weighted_mut + /// [`choose`]: IndexedRandom::choose + /// [`choose_weighted_mut`]: IndexedMutRandom::choose_weighted_mut /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted( &self, rng: &mut R, weight: F, - ) -> Result<&Self::Item, WeightedError> + ) -> Result<&Self::Output, WeightError> where R: Rng + ?Sized, - F: Fn(&Self::Item) -> B, + F: Fn(&Self::Output) -> B, B: SampleBorrow, - X: SampleUniform + Weight + ::core::cmp::PartialOrd; - - /// Biased sampling for one element (mut) - /// - /// Returns a mutable reference to one element of the slice, sampled according - /// to the provided weights. Returns `None` only if the slice is empty. - /// - /// The specified function `weight` maps each item `x` to a relative - /// likelihood `weight(x)`. The probability of each item being selected is - /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. - /// - /// For slices of length `n`, complexity is `O(n)`. - /// For more information about the underlying algorithm, - /// see [`distributions::WeightedIndex`]. - /// - /// See also [`choose_weighted`]. - /// - /// [`choose_mut`]: SliceRandom::choose_mut - /// [`choose_weighted`]: SliceRandom::choose_weighted - /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex - #[cfg(feature = "alloc")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] - fn choose_weighted_mut( - &mut self, rng: &mut R, weight: F, - ) -> Result<&mut Self::Item, WeightedError> - where - R: Rng + ?Sized, - F: Fn(&Self::Item) -> B, - B: SampleBorrow, - X: SampleUniform + Weight + ::core::cmp::PartialOrd; + X: SampleUniform + Weight + ::core::cmp::PartialOrd, + { + use crate::distributions::{Distribution, WeightedIndex}; + let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; + Ok(&self[distr.sample(rng)]) + } /// Biased sampling of `amount` distinct elements /// @@ -232,7 +211,7 @@ pub trait SliceRandom { /// // (25% * 33%) + (25% * 33%) = 16.6% chance that the output is `['b', 'c']` in some order. /// println!("{:?}", choices.choose_multiple_weighted(&mut rng, 2, |item| item.1).unwrap().collect::>()); /// ``` - /// [`choose_multiple`]: SliceRandom::choose_multiple + /// [`choose_multiple`]: IndexedRandom::choose_multiple // // Note: this is feature-gated on std due to usage of f64::powf. // If necessary, we may use alloc+libm as an alternative (see PR #1089). @@ -240,12 +219,106 @@ pub trait SliceRandom { #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] fn choose_multiple_weighted( &self, rng: &mut R, amount: usize, weight: F, - ) -> Result, WeightedError> + ) -> Result, WeightError> + where + Self::Output: Sized, + R: Rng + ?Sized, + F: Fn(&Self::Output) -> X, + X: Into, + { + let amount = ::core::cmp::min(amount, self.len()); + Ok(SliceChooseIter { + slice: self, + _phantom: Default::default(), + indices: index::sample_weighted( + rng, + self.len(), + |idx| weight(&self[idx]).into(), + amount, + )? + .into_iter(), + }) + } +} + +/// Extension trait on indexable lists, providing random sampling methods. +/// +/// This trait is implemented automatically for every type implementing +/// [`IndexedRandom`] and [`std::ops::IndexMut`]. +pub trait IndexedMutRandom: IndexedRandom + IndexMut { + /// Uniformly sample one element (mut) + /// + /// Returns a mutable reference to one uniformly-sampled random element of + /// the slice, or `None` if the slice is empty. + /// + /// For slices, complexity is `O(1)`. + fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Output> + where + R: Rng + ?Sized, + { + if self.is_empty() { + None + } else { + let len = self.len(); + Some(&mut self[gen_index(rng, len)]) + } + } + + /// Biased sampling for one element (mut) + /// + /// Returns a mutable reference to one element of the slice, sampled according + /// to the provided weights. Returns `None` only if the slice is empty. + /// + /// The specified function `weight` maps each item `x` to a relative + /// likelihood `weight(x)`. The probability of each item being selected is + /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. + /// + /// For slices of length `n`, complexity is `O(n)`. + /// For more information about the underlying algorithm, + /// see [`distributions::WeightedIndex`]. + /// + /// See also [`choose_weighted`]. + /// + /// [`choose_mut`]: IndexedMutRandom::choose_mut + /// [`choose_weighted`]: IndexedRandom::choose_weighted + /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex + #[cfg(feature = "alloc")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] + fn choose_weighted_mut( + &mut self, rng: &mut R, weight: F, + ) -> Result<&mut Self::Output, WeightError> where R: Rng + ?Sized, - F: Fn(&Self::Item) -> X, - X: Into; + F: Fn(&Self::Output) -> B, + B: SampleBorrow, + X: SampleUniform + Weight + ::core::cmp::PartialOrd, + { + use crate::distributions::{Distribution, WeightedIndex}; + let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; + let index = distr.sample(rng); + Ok(&mut self[index]) + } +} +/// Extension trait on slices, providing shuffling methods. +/// +/// This trait is implemented on all `[T]` slice types, providing several +/// methods for choosing and shuffling elements. You must `use` this trait: +/// +/// ``` +/// use rand::seq::SliceRandom; +/// +/// let mut rng = rand::thread_rng(); +/// let mut bytes = "Hello, random!".to_string().into_bytes(); +/// bytes.shuffle(&mut rng); +/// let str = String::from_utf8(bytes).unwrap(); +/// println!("{}", str); +/// ``` +/// Example output (non-deterministic): +/// ```none +/// l,nmroHado !le +/// ``` +pub trait SliceRandom: IndexedMutRandom { /// Shuffle a mutable slice in place. /// /// For slices of length `n`, complexity is `O(n)`. @@ -286,8 +359,9 @@ pub trait SliceRandom { /// For slices, complexity is `O(m)` where `m = amount`. fn partial_shuffle( &mut self, rng: &mut R, amount: usize, - ) -> (&mut [Self::Item], &mut [Self::Item]) + ) -> (&mut [Self::Output], &mut [Self::Output]) where + Self::Output: Sized, R: Rng + ?Sized; } @@ -460,7 +534,7 @@ pub trait IteratorRandom: Iterator + Sized { /// case this equals the number of elements available. /// /// Complexity is `O(n)` where `n` is the length of the iterator. - /// For slices, prefer [`SliceRandom::choose_multiple`]. + /// For slices, prefer [`IndexedRandom::choose_multiple`]. fn choose_multiple_fill(mut self, rng: &mut R, buf: &mut [Self::Item]) -> usize where R: Rng + ?Sized, @@ -500,7 +574,7 @@ pub trait IteratorRandom: Iterator + Sized { /// elements available. /// /// Complexity is `O(n)` where `n` is the length of the iterator. - /// For slices, prefer [`SliceRandom::choose_multiple`]. + /// For slices, prefer [`IndexedRandom::choose_multiple`]. #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(mut self, rng: &mut R, amount: usize) -> Vec @@ -530,98 +604,15 @@ pub trait IteratorRandom: Iterator + Sized { } } -impl SliceRandom for [T] { - type Item = T; - - fn choose(&self, rng: &mut R) -> Option<&Self::Item> - where - R: Rng + ?Sized, - { - if self.is_empty() { - None - } else { - Some(&self[gen_index(rng, self.len())]) - } - } - - fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> - where - R: Rng + ?Sized, - { - if self.is_empty() { - None - } else { - let len = self.len(); - Some(&mut self[gen_index(rng, len)]) - } - } - - #[cfg(feature = "alloc")] - fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter - where - R: Rng + ?Sized, - { - let amount = ::core::cmp::min(amount, self.len()); - SliceChooseIter { - slice: self, - _phantom: Default::default(), - indices: index::sample(rng, self.len(), amount).into_iter(), - } - } - - #[cfg(feature = "alloc")] - fn choose_weighted( - &self, rng: &mut R, weight: F, - ) -> Result<&Self::Item, WeightedError> - where - R: Rng + ?Sized, - F: Fn(&Self::Item) -> B, - B: SampleBorrow, - X: SampleUniform + Weight + ::core::cmp::PartialOrd, - { - use crate::distributions::{Distribution, WeightedIndex}; - let distr = WeightedIndex::new(self.iter().map(weight))?; - Ok(&self[distr.sample(rng)]) +impl IndexedRandom for [T] { + fn len(&self) -> usize { + self.len() } +} - #[cfg(feature = "alloc")] - fn choose_weighted_mut( - &mut self, rng: &mut R, weight: F, - ) -> Result<&mut Self::Item, WeightedError> - where - R: Rng + ?Sized, - F: Fn(&Self::Item) -> B, - B: SampleBorrow, - X: SampleUniform + Weight + ::core::cmp::PartialOrd, - { - use crate::distributions::{Distribution, WeightedIndex}; - let distr = WeightedIndex::new(self.iter().map(weight))?; - Ok(&mut self[distr.sample(rng)]) - } - - #[cfg(feature = "std")] - fn choose_multiple_weighted( - &self, rng: &mut R, amount: usize, weight: F, - ) -> Result, WeightedError> - where - R: Rng + ?Sized, - F: Fn(&Self::Item) -> X, - X: Into, - { - let amount = ::core::cmp::min(amount, self.len()); - Ok(SliceChooseIter { - slice: self, - _phantom: Default::default(), - indices: index::sample_weighted( - rng, - self.len(), - |idx| weight(&self[idx]).into(), - amount, - )? - .into_iter(), - }) - } +impl + ?Sized> IndexedMutRandom for IR {} +impl SliceRandom for [T] { fn shuffle(&mut self, rng: &mut R) where R: Rng + ?Sized, @@ -635,7 +626,7 @@ impl SliceRandom for [T] { fn partial_shuffle( &mut self, rng: &mut R, amount: usize, - ) -> (&mut [Self::Item], &mut [Self::Item]) + ) -> (&mut [T], &mut [T]) where R: Rng + ?Sized, { @@ -672,7 +663,7 @@ impl IteratorRandom for I where I: Iterator + Sized {} /// An iterator over multiple slice elements. /// /// This struct is created by -/// [`SliceRandom::choose_multiple`](trait.SliceRandom.html#tymethod.choose_multiple). +/// [`IndexedRandom::choose_multiple`](trait.IndexedRandom.html#tymethod.choose_multiple). #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Debug)] @@ -1187,23 +1178,23 @@ mod test { let empty_slice = &mut [10][0..0]; assert_eq!( empty_slice.choose_weighted(&mut r, |_| 1), - Err(WeightedError::NoItem) + Err(WeightError::InvalidInput) ); assert_eq!( empty_slice.choose_weighted_mut(&mut r, |_| 1), - Err(WeightedError::NoItem) + Err(WeightError::InvalidInput) ); assert_eq!( ['x'].choose_weighted_mut(&mut r, |_| 0), - Err(WeightedError::AllWeightsZero) + Err(WeightError::InsufficientNonZero) ); assert_eq!( [0, -1].choose_weighted_mut(&mut r, |x| *x), - Err(WeightedError::InvalidWeight) + Err(WeightError::InvalidWeight) ); assert_eq!( [-1, 0].choose_weighted_mut(&mut r, |x| *x), - Err(WeightedError::InvalidWeight) + Err(WeightError::InvalidWeight) ); } @@ -1340,42 +1331,23 @@ mod test { // Case 2: All of the weights are 0 let choices = [('a', 0), ('b', 0), ('c', 0)]; - - assert_eq!( - choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap() - .count(), - 2 - ); + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert_eq!(r.unwrap_err(), WeightError::InsufficientNonZero); // Case 3: Negative weights let choices = [('a', -1), ('b', 1), ('c', 1)]; - assert_eq!( - choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap_err(), - WeightedError::InvalidWeight - ); + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); // Case 4: Empty list let choices = []; - assert_eq!( - choices - .choose_multiple_weighted(&mut rng, 0, |_: &()| 0) - .unwrap() - .count(), - 0 - ); + let r = choices.choose_multiple_weighted(&mut rng, 0, |_: &()| 0); + assert_eq!(r.unwrap().count(), 0); // Case 5: NaN weights let choices = [('a', core::f64::NAN), ('b', 1.0), ('c', 1.0)]; - assert_eq!( - choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap_err(), - WeightedError::InvalidWeight - ); + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); // Case 6: +infinity weights let choices = [('a', core::f64::INFINITY), ('b', 1.0), ('c', 1.0)]; @@ -1390,18 +1362,13 @@ mod test { // Case 7: -infinity weights let choices = [('a', core::f64::NEG_INFINITY), ('b', 1.0), ('c', 1.0)]; - assert_eq!( - choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap_err(), - WeightedError::InvalidWeight - ); + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); // Case 8: -0 weights let choices = [('a', -0.0), ('b', 1.0), ('c', 1.0)]; - assert!(choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .is_ok()); + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert!(r.is_ok()); } #[test] From 923bcf1711437a024db4e667875cd1518436f2ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 10:20:46 +0000 Subject: [PATCH 340/443] Update serde_with requirement from 1.14.0 to 3.6.1 (#1392) --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 8339580a504..2c71ac1672d 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -30,7 +30,7 @@ serde1 = ["serde", "rand/serde1"] rand = { path = "..", version = "0.9.0", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } -serde_with = { version = "1.14.0", optional = true } +serde_with = { version = "3.6.1", optional = true } [dev-dependencies] rand_pcg = { version = "0.4.0", path = "../rand_pcg" } From 2e13129545925c221a56d34a93740610dbc04a7e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 18 Feb 2024 10:47:32 +0000 Subject: [PATCH 341/443] Bump actions upload-pages-artifact, deploy-pages (#1396) --- .github/workflows/gh-pages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index e106f4b5b16..9b48b622fa5 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -37,10 +37,10 @@ jobs: uses: actions/configure-pages@v4 - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: './target/doc' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 From 8aa456cee9d3905d424e7f175421ef26f097d7a4 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 18 Feb 2024 11:33:35 +0000 Subject: [PATCH 342/443] Fix gh-pages.yml by deleting .lock file This file is does not meet actions/upload-pages-artifact permission requirements. --- .github/workflows/gh-pages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 9b48b622fa5..6c78ff56baf 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -32,6 +32,7 @@ jobs: run: | cargo doc --all --features nightly,serde1,getrandom,small_rng cp utils/redirect.html target/doc/index.html + rm target/doc/.lock - name: Setup Pages uses: actions/configure-pages@v4 From 5577003615af6ec9255c680467d34f60a2149bb7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 18 Feb 2024 17:02:05 +0000 Subject: [PATCH 343/443] Update CHANGELOGs and prepare rand 0.9.0-alpha.0 (#1395) Also: add pull-request template --- .github/PULL_REQUEST_TEMPLATE.md | 7 +++++++ CHANGELOG.md | 36 ++++++++++++++++++++++++++++---- Cargo.toml | 8 +++---- rand_chacha/CHANGELOG.md | 8 ++++--- rand_chacha/Cargo.toml | 4 ++-- rand_core/CHANGELOG.md | 9 ++++++++ rand_core/Cargo.toml | 2 +- rand_distr/CHANGELOG.md | 19 +++++++++++++---- rand_distr/Cargo.toml | 8 +++---- rand_pcg/CHANGELOG.md | 4 +++- rand_pcg/Cargo.toml | 6 +++--- 11 files changed, 85 insertions(+), 26 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..02ac88f0673 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +- [ ] Added a `CHANGELOG.md` entry + +# Summary + +# Motivation + +# Details diff --git a/CHANGELOG.md b/CHANGELOG.md index 8706df8c639..aaccde2ce79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,16 +8,44 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. -## [0.9.0] - unreleased +## [0.9.0-alpha.0] - 2024-02-18 +This is a pre-release. To depend on this version, use `rand = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). + +### Generators +- Change `SmallRng::seed_from_u64` implementation (#1203) +- Replace `SeedableRng` impl for `SmallRng` with inherent methods, excluding `fn from_seed` (#1368) + +### Sequences +- Simpler and faster implementation of Floyd's F2 (#1277). This + changes some outputs from `rand::seq::index::sample` and + `rand::seq::SliceRandom::choose_multiple`. +- New, faster algorithms for `IteratorRandom::choose` and `choose_stable` (#1268) +- New, faster algorithms for `SliceRandom::shuffle` and `partial_shuffle` (#1272) +- Re-introduce `Rng::gen_iter` (#1305) +- Split trait `SliceRandom` into `IndexedRandom`, `IndexedMutRandom`, `SliceRandom` (#1382) + ### Distributions - `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229) - `Uniform` implements `TryFrom` instead of `From` for ranges (#1229) - `Uniform` now uses Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287) +- Relax `Sized` bound on `Distribution for &D` (#1278) +- Explicit impl of `sample_single_inclusive` (+~20% perf) (#1289) +- Impl `DistString` for `Slice` and `Uniform` (#1315) +- Let `Standard` support all `NonZero*` types (#1332) +- Add `trait Weight`, allowing `WeightedIndex` to trap overflow (#1353) +- Rename `WeightedError` to `WeightError`, revising variants (#1382) + +### SIMD +- Switch to `std::simd`, expand SIMD & docs (#1239) +- Optimise SIMD widening multipy (#1247) ### Other -- Simpler and faster implementation of Floyd's F2 (#1277). This - changes some outputs from `rand::seq::index::sample` and - `rand::seq::SliceRandom::choose_multiple`. +- Bump MSRV to 1.60.0 (#1207, #1246, #1269, #1341) +- Improve `thread_rng` related docs (#1257) +- Add `Cargo.lock.msrv` file (#1275) +- Docs: enable experimental `--generate-link-to-definition` feature (#1327) +- Use `zerocopy` to replace some `unsafe` code (#1349) +- Support `std` feature without `getrandom` or `rand_chacha` (#1354) ## [0.8.5] - 2021-08-20 ### Fixes diff --git a/Cargo.toml b/Cargo.toml index b2c7f3d6162..13043a2f40b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.9.0" +version = "0.9.0-alpha.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -65,10 +65,10 @@ members = [ ] [dependencies] -rand_core = { path = "rand_core", version = "0.7.0", default-features = false } +rand_core = { path = "rand_core", version = "=0.9.0-alpha.0", default-features = false } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } -rand_chacha = { path = "rand_chacha", version = "0.4.0", default-features = false, optional = true } +rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.0", default-features = false, optional = true } zerocopy = { version = "=0.8.0-alpha.5", default-features = false, features = ["simd"] } [target.'cfg(unix)'.dependencies] @@ -76,7 +76,7 @@ zerocopy = { version = "=0.8.0-alpha.5", default-features = false, features = [" libc = { version = "0.2.22", optional = true, default-features = false } [dev-dependencies] -rand_pcg = { path = "rand_pcg", version = "0.4.0" } +rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.0" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index 7ef621f6781..dcc9d2e688c 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,9 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] -- Made `rand_chacha` propagate the `std` feature down to `rand_core` -- Performance improvements for AVX2: ~4-7% +## [0.9.0-alpha.0] - 2024-02-18 +This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). + +- Made `rand_chacha` propagate the `std` feature down to `rand_core` (#1153) +- Remove usage of `unsafe` in `fn generate` (#1181) then optimise for AVX2 (~4-7%) (#1192) ## [0.3.1] - 2021-06-09 - add getters corresponding to existing setters: `get_seed`, `get_stream` (#1124) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 847cfdde686..bcc09f61cc0 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_chacha" -version = "0.4.0" +version = "0.9.0-alpha.0" authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -19,7 +19,7 @@ rust-version = "1.60" rustdoc-args = ["--generate-link-to-definition"] [dependencies] -rand_core = { path = "../rand_core", version = "0.7.0" } +rand_core = { path = "../rand_core", version = "=0.9.0-alpha.0" } ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index 75fcbc667c6..b7f00895622 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0-alpha.0] - 2024-02-18 +This is a pre-release. To depend on this version, use `rand_core = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). + +- Bump MSRV to 1.60.0 (#1207, #1246, #1269, #1341) +- Allow `rand_core::impls::fill_via_u*_chunks` to mutate source (#1182) +- Add `fn RngCore::read_adapter` implementing `std::io::Read` (#1267) +- Add `trait CryptoBlockRng: BlockRngCore`; make `trait CryptoRng: RngCore` (#1273) +- Use `zerocopy` to replace some `unsafe` code (#1349, #1393) + ## [0.6.4] - 2022-09-15 - Fix unsoundness in `::next_u32` (#1160) - Reduce use of `unsafe` and improve gen_bytes performance (#1180) diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index b53a01634b5..28845f3d661 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.7.0" +version = "0.9.0-alpha.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 7d6c0602e17..24b43648ebd 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,15 +4,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.5.0] - unreleased +## [0.5.0-alpha.0] - 2024-02-18 +This is a pre-release. To depend on this version, use `rand_distr = "=0.5.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). + +### Additions +- Make distributions comparable with `PartialEq` (#1218) +- Add `WeightedIndexTree` (#1372) + +### Changes +- Target `rand` version `0.9.0-alpha.0` - Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) This breaks serialization compatibility with older versions. -- Upgrade Rand -- Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda -- Fix `Poisson` distribution instantiation so it return an error if lambda is infinite - `Dirichlet` now uses `const` generics, which means that its size is required at compile time (#1292) - The `Dirichlet::new_with_size` constructor was removed (#1292) +### Fixes +- Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda (#1284) +- Fix `Poisson` distribution instantiation so it return an error if lambda is infinite (#1291) +- Fix Dirichlet sample for small alpha values to avoid NaN samples (#1209) +- Fix infinite loop in `Binomial` distribution (#1325) + ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 2c71ac1672d..36533d46464 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.5.0" +version = "0.5.0-alpha.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -27,15 +27,15 @@ std_math = ["num-traits/std"] serde1 = ["serde", "rand/serde1"] [dependencies] -rand = { path = "..", version = "0.9.0", default-features = false } +rand = { path = "..", version = "=0.9.0-alpha.0", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } serde_with = { version = "3.6.1", optional = true } [dev-dependencies] -rand_pcg = { version = "0.4.0", path = "../rand_pcg" } +rand_pcg = { version = "=0.9.0-alpha.0", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "0.9.0", features = ["small_rng"] } +rand = { path = "..", version = "=0.9.0-alpha.0", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.13", features = [ "std" ] } # Special functions for testing distributions diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index 63a37781707..c60386cf0bc 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.9.0-alpha.0] - 2024-02-18 +This is a pre-release. To depend on this version, use `rand_pcg = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). + - Add `Lcg128CmDxsm64` generator compatible with NumPy's `PCG64DXSM` (#1202) - Add examples for initializing the RNGs diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 757cc8e326c..3f5021796c4 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_pcg" -version = "0.4.0" +version = "0.9.0-alpha.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -22,11 +22,11 @@ rustdoc-args = ["--generate-link-to-definition"] serde1 = ["serde"] [dependencies] -rand_core = { path = "../rand_core", version = "0.7.0" } +rand_core = { path = "../rand_core", version = "=0.9.0-alpha.0" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] -rand = { path = "..", version = "0.9" } +rand = { path = "..", version = "=0.9.0-alpha.0" } # This is for testing serde, unfortunately we can't specify feature-gated dev # deps yet, see: https://github.com/rust-lang/cargo/issues/1596 # Versions prior to 1.1.4 had incorrect minimal dependencies. From b1427c6447ce6f2aaeda967f777d6daebed18164 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 18 Feb 2024 17:32:29 +0000 Subject: [PATCH 344/443] Disable rand_pcg's dev dependency on rand (#1397) The circular dependency prevents publishing --- rand_pcg/Cargo.toml | 1 - rand_pcg/src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 3f5021796c4..1fa514344bf 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -26,7 +26,6 @@ rand_core = { path = "../rand_core", version = "=0.9.0-alpha.0" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] -rand = { path = "..", version = "=0.9.0-alpha.0" } # This is for testing serde, unfortunately we can't specify feature-gated dev # deps yet, see: https://github.com/rust-lang/cargo/issues/1596 # Versions prior to 1.1.4 had incorrect minimal dependencies. diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index c1484c2d55e..e67728a90e6 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -43,7 +43,7 @@ //! The functionality of this crate is implemented using traits from the `rand_core` crate, but you may use the `rand` //! crate for further functionality to initialize the generator from various sources and to generate random values: //! -//! ``` +//! ```ignore //! use rand::{Rng, SeedableRng}; //! use rand_pcg::Pcg64Mcg; //! From 7ff0fc9e5949468fb7095627bd6d9f767be4ce4f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 18 Feb 2024 17:51:59 +0000 Subject: [PATCH 345/443] Add path for benchmark (#1398) --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 13043a2f40b..1d8cfbe057c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ criterion = { version = "0.4" } [[bench]] name = "uniform" +path = "benches/uniform.rs" harness = false [[bench]] From 304048bdd90a41d7effb6cafc9a835fd3fcf87c5 Mon Sep 17 00:00:00 2001 From: Michael Dyer <59163924+MichaelOwenDyer@users.noreply.github.com> Date: Thu, 7 Mar 2024 09:34:53 +0100 Subject: [PATCH 346/443] Fix redundant import warnings (#1405) --- rand_core/src/block.rs | 1 - rand_core/src/le.rs | 2 -- rand_core/src/lib.rs | 3 --- src/distributions/float.rs | 1 - src/distributions/other.rs | 1 - src/distributions/uniform.rs | 1 - src/distributions/weighted_index.rs | 1 - src/seq/mod.rs | 2 -- 8 files changed, 12 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index a8cefc8e40c..9122f9bc675 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -55,7 +55,6 @@ use crate::impls::{fill_via_u32_chunks, fill_via_u64_chunks}; use crate::{Error, CryptoRng, RngCore, SeedableRng}; -use core::convert::AsRef; use core::fmt; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; diff --git a/rand_core/src/le.rs b/rand_core/src/le.rs index ed42e57f478..89e5e729c8a 100644 --- a/rand_core/src/le.rs +++ b/rand_core/src/le.rs @@ -11,8 +11,6 @@ //! Little-Endian order has been chosen for internal usage; this makes some //! useful functions available. -use core::convert::TryInto; - /// Reads unsigned 32 bit integers from `src` into `dst`. #[inline] pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 292c57ffb8b..d42ab8d63ef 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -38,9 +38,6 @@ #![cfg_attr(doc_cfg, feature(doc_cfg))] #![no_std] -use core::convert::AsMut; -use core::default::Default; - #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "std")] extern crate std; #[cfg(feature = "alloc")] use alloc::boxed::Box; diff --git a/src/distributions/float.rs b/src/distributions/float.rs index 37b71612a1b..4414cc74988 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -172,7 +172,6 @@ float_impls! { f64x8, u64x8, f64, u64, 52, 1023 } #[cfg(test)] mod tests { use super::*; - use crate::distributions::utils::FloatAsSIMD; use crate::rngs::mock::StepRng; const EPSILON32: f32 = ::core::f32::EPSILON; diff --git a/src/distributions/other.rs b/src/distributions/other.rs index ebe3d57ed3f..596b8962c1e 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -281,7 +281,6 @@ where Standard: Distribution mod tests { use super::*; use crate::RngCore; - #[cfg(feature = "alloc")] use alloc::string::String; #[test] fn test_misc() { diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 5e6b6ae3f9e..ecf90520b21 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -106,7 +106,6 @@ use core::fmt; use core::time::Duration; use core::ops::{Range, RangeInclusive}; -use core::convert::TryFrom; use crate::distributions::float::IntoFloat; use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD, WideningMultiply}; diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 49cb02d6ade..59ff1c1915d 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -11,7 +11,6 @@ use crate::distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler}; use crate::distributions::Distribution; use crate::Rng; -use core::cmp::PartialOrd; use core::fmt; // Note that this whole module is only imported if feature="alloc" is enabled. diff --git a/src/seq/mod.rs b/src/seq/mod.rs index f5cbc6008e9..908021ad3dc 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -712,8 +712,6 @@ fn gen_index(rng: &mut R, ubound: usize) -> usize { #[cfg(test)] mod test { use super::*; - #[cfg(feature = "alloc")] - use crate::Rng; #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; From 7ed92ee2781e6887799b8f455a4670a6bd1e46ab Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Thu, 7 Mar 2024 09:39:45 +0100 Subject: [PATCH 347/443] Add .choices() method to the Slice distribution (#1402) Signed-off-by: Justus Fluegel --- CHANGELOG.md | 3 +++ src/distributions/slice.rs | 22 +++++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaccde2ce79..b431a9ef73a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [0.9.1] - unreleased +- Add the `Slice::num_choices` method to the Slice distribution (#1402) + ## [0.9.0-alpha.0] - 2024-02-18 This is a pre-release. To depend on this version, use `rand = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 5fc08751f6c..1c96680aa64 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -6,6 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use core::num::NonZeroUsize; + use crate::distributions::{Distribution, Uniform}; #[cfg(feature = "alloc")] use alloc::string::String; @@ -67,19 +69,25 @@ use alloc::string::String; pub struct Slice<'a, T> { slice: &'a [T], range: Uniform, + num_choices: NonZeroUsize, } impl<'a, T> Slice<'a, T> { /// Create a new `Slice` instance which samples uniformly from the slice. /// Returns `Err` if the slice is empty. pub fn new(slice: &'a [T]) -> Result { - match slice.len() { - 0 => Err(EmptySlice), - len => Ok(Self { - slice, - range: Uniform::new(0, len).unwrap(), - }), - } + let num_choices = NonZeroUsize::new(slice.len()).ok_or(EmptySlice)?; + + Ok(Self { + slice, + range: Uniform::new(0, num_choices.get()).unwrap(), + num_choices, + }) + } + + /// Returns the count of choices in this distribution + pub fn num_choices(&self) -> NonZeroUsize { + self.num_choices } } From 4ed1b20be05fc32b5d4867af64953d318e2b41f9 Mon Sep 17 00:00:00 2001 From: acceptacross <150119116+acceptacross@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:56:13 +0800 Subject: [PATCH 348/443] chore: fix typos (#1407) Signed-off-by: acceptacross --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b431a9ef73a..1251c69db1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ This is a pre-release. To depend on this version, use `rand = "=0.9.0-alpha.0"` ### SIMD - Switch to `std::simd`, expand SIMD & docs (#1239) -- Optimise SIMD widening multipy (#1247) +- Optimise SIMD widening multiply (#1247) ### Other - Bump MSRV to 1.60.0 (#1207, #1246, #1269, #1341) From 769bcb6e4c73dce7f57080cc53acc3f9d4c9c60f Mon Sep 17 00:00:00 2001 From: TheIronBorn Date: Mon, 18 Mar 2024 08:42:42 +0000 Subject: [PATCH 349/443] Document more crate feature usage (#1411) --- Cargo.toml | 2 +- rand_core/src/lib.rs | 1 + src/distributions/distribution.rs | 1 + src/distributions/float.rs | 27 ++++++++++++++-------- src/distributions/integer.rs | 22 ++++++++++++++---- src/distributions/mod.rs | 1 + src/distributions/other.rs | 5 ++-- src/distributions/slice.rs | 1 + src/distributions/uniform.rs | 38 +++++++++++++++++++------------ src/seq/mod.rs | 13 ++++++----- 10 files changed, 74 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1d8cfbe057c..3e9d4a1173a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] # To build locally: -# RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --generate-link-to-definition --open +# RUSTDOCFLAGS="--cfg doc_cfg -Zunstable-options --generate-link-to-definition" cargo +nightly doc --all --all-features --no-deps --open all-features = true rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"] diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index d42ab8d63ef..70424e6c9c0 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -481,6 +481,7 @@ impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {} // Implement `CryptoRng` for boxed references to a `CryptoRng`. #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl CryptoRng for Box {} #[cfg(test)] diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index 18ab30b8860..175e355ac8d 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -186,6 +186,7 @@ where /// Sampling a `String` of random characters is not quite the same as collecting /// a sequence of chars. This trait contains some helpers. #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub trait DistString { /// Append `len` random chars to `string` fn append_string(&self, rng: &mut R, string: &mut String, len: usize); diff --git a/src/distributions/float.rs b/src/distributions/float.rs index 4414cc74988..da7321ee866 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -90,8 +90,9 @@ pub trait IntoFloat { } macro_rules! float_impls { - ($ty:ident, $uty:ident, $f_scalar:ident, $u_scalar:ty, + ($($meta:meta)?, $ty:ident, $uty:ident, $f_scalar:ident, $u_scalar:ty, $fraction_bits:expr, $exponent_bias:expr) => { + $(#[cfg($meta)])? impl IntoFloat for $uty { type F = $ty; #[inline(always)] @@ -103,6 +104,8 @@ macro_rules! float_impls { } } + $(#[cfg($meta)] + #[cfg_attr(doc_cfg, doc(cfg($meta)))])? impl Distribution<$ty> for Standard { fn sample(&self, rng: &mut R) -> $ty { // Multiply-based method; 24/53 random bits; [0, 1) interval. @@ -118,6 +121,8 @@ macro_rules! float_impls { } } + $(#[cfg($meta)] + #[cfg_attr(doc_cfg, doc(cfg($meta)))])? impl Distribution<$ty> for OpenClosed01 { fn sample(&self, rng: &mut R) -> $ty { // Multiply-based method; 24/53 random bits; (0, 1] interval. @@ -134,6 +139,8 @@ macro_rules! float_impls { } } + $(#[cfg($meta)] + #[cfg_attr(doc_cfg, doc(cfg($meta)))])? impl Distribution<$ty> for Open01 { fn sample(&self, rng: &mut R) -> $ty { // Transmute-based method; 23/52 random bits; (0, 1) interval. @@ -150,24 +157,24 @@ macro_rules! float_impls { } } -float_impls! { f32, u32, f32, u32, 23, 127 } -float_impls! { f64, u64, f64, u64, 52, 1023 } +float_impls! { , f32, u32, f32, u32, 23, 127 } +float_impls! { , f64, u64, f64, u64, 52, 1023 } #[cfg(feature = "simd_support")] -float_impls! { f32x2, u32x2, f32, u32, 23, 127 } +float_impls! { feature = "simd_support", f32x2, u32x2, f32, u32, 23, 127 } #[cfg(feature = "simd_support")] -float_impls! { f32x4, u32x4, f32, u32, 23, 127 } +float_impls! { feature = "simd_support", f32x4, u32x4, f32, u32, 23, 127 } #[cfg(feature = "simd_support")] -float_impls! { f32x8, u32x8, f32, u32, 23, 127 } +float_impls! { feature = "simd_support", f32x8, u32x8, f32, u32, 23, 127 } #[cfg(feature = "simd_support")] -float_impls! { f32x16, u32x16, f32, u32, 23, 127 } +float_impls! { feature = "simd_support", f32x16, u32x16, f32, u32, 23, 127 } #[cfg(feature = "simd_support")] -float_impls! { f64x2, u64x2, f64, u64, 52, 1023 } +float_impls! { feature = "simd_support", f64x2, u64x2, f64, u64, 52, 1023 } #[cfg(feature = "simd_support")] -float_impls! { f64x4, u64x4, f64, u64, 52, 1023 } +float_impls! { feature = "simd_support", f64x4, u64x4, f64, u64, 52, 1023 } #[cfg(feature = "simd_support")] -float_impls! { f64x8, u64x8, f64, u64, 52, 1023 } +float_impls! { feature = "simd_support", f64x8, u64x8, f64, u64, 52, 1023 } #[cfg(test)] mod tests { diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index e5d320b2550..8b9ae4ad403 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -124,8 +124,9 @@ impl_nzint!(NonZeroI128, NonZeroI128::new); impl_nzint!(NonZeroIsize, NonZeroIsize::new); macro_rules! x86_intrinsic_impl { - ($($intrinsic:ident),+) => {$( - /// Available only on x86/64 platforms + ($meta:meta, $($intrinsic:ident),+) => {$( + #[cfg($meta)] + #[cfg_attr(doc_cfg, doc(cfg($meta)))] impl Distribution<$intrinsic> for Standard { #[inline] fn sample(&self, rng: &mut R) -> $intrinsic { @@ -146,6 +147,9 @@ macro_rules! simd_impl { /// Requires nightly Rust and the [`simd_support`] feature /// /// [`simd_support`]: https://github.com/rust-random/rand#crate-features + #[cfg(feature = "simd_support")] + // TODO: as doc_cfg/doc_auto_cfg mature ensure they catch this + #[cfg_attr(doc_cfg, doc(cfg(feature = "simd_support")))] impl Distribution> for Standard where LaneCount: SupportedLaneCount, @@ -164,12 +168,22 @@ macro_rules! simd_impl { simd_impl!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -x86_intrinsic_impl!(__m128i, __m256i); +x86_intrinsic_impl!( + any(target_arch = "x86", target_arch = "x86_64"), + __m128i, + __m256i +); #[cfg(all( any(target_arch = "x86", target_arch = "x86_64"), feature = "simd_support" ))] -x86_intrinsic_impl!(__m512i); +x86_intrinsic_impl!( + all( + any(target_arch = "x86", target_arch = "x86_64"), + feature = "simd_support" + ), + __m512i +); #[cfg(test)] mod tests { diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 39d967d4f60..ee3615cf922 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -112,6 +112,7 @@ pub mod uniform; pub use self::bernoulli::{Bernoulli, BernoulliError}; pub use self::distribution::{Distribution, DistIter, DistMap}; #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use self::distribution::DistString; pub use self::float::{Open01, OpenClosed01}; pub use self::other::Alphanumeric; diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 596b8962c1e..5b922bd37e2 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -98,6 +98,7 @@ impl Distribution for Standard { /// Note: the `String` is potentially left with excess capacity; optionally the /// user may call `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl DistString for Standard { fn append_string(&self, rng: &mut R, s: &mut String, len: usize) { // A char is encoded with at most four bytes, thus this reservation is @@ -128,6 +129,7 @@ impl Distribution for Alphanumeric { } #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl DistString for Alphanumeric { fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { unsafe { @@ -148,8 +150,6 @@ impl Distribution for Standard { } } -/// Requires nightly Rust and the [`simd_support`] feature -/// /// Note that on some hardware like x86/64 mask operations like [`_mm_blendv_epi8`] /// only care about a single bit. This means that you could use uniform random bits /// directly: @@ -177,6 +177,7 @@ impl Distribution for Standard { /// [`_mm_blendv_epi8`]: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8&ig_expand=514/ /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[cfg(feature = "simd_support")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "simd_support")))] impl Distribution> for Standard where T: MaskElement + Default, diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 1c96680aa64..d49f45ccebc 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -129,6 +129,7 @@ impl std::error::Error for EmptySlice {} /// Note: the `String` is potentially left with excess capacity; optionally the /// user may call `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl<'a> super::DistString for Slice<'a, char> { fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { // Get the max char length to minimize extra space. diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index ecf90520b21..e69d5fe0200 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -604,7 +604,7 @@ macro_rules! uniform_int_impl { let (mut result, mut lo) = rng.gen::<$sample_ty>().wmul(range); - // In constrast to the biased sampler, we use a loop: + // In contrast to the biased sampler, we use a loop: while lo > range.wrapping_neg() { let (new_hi, new_lo) = (rng.gen::<$sample_ty>()).wmul(range); match lo.checked_add(new_hi) { @@ -652,6 +652,9 @@ macro_rules! uniform_simd_int_impl { // know the PRNG's minimal output size, and casting to a larger vector // is generally a bad idea for SIMD performance. The user can still // implement it manually. + + #[cfg(feature = "simd_support")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "simd_support")))] impl SampleUniform for Simd<$ty, LANES> where LaneCount: SupportedLaneCount, @@ -662,6 +665,8 @@ macro_rules! uniform_simd_int_impl { type Sampler = UniformInt>; } + #[cfg(feature = "simd_support")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "simd_support")))] impl UniformSampler for UniformInt> where LaneCount: SupportedLaneCount, @@ -839,11 +844,12 @@ impl UniformSampler for UniformChar { } } -/// Note: the `String` is potentially left with excess capacity if the range -/// includes non ascii chars; optionally the user may call +/// Note: the `String` is potentially left with excess capacity if the range +/// includes non ascii chars; optionally the user may call /// `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -impl super::DistString for Uniform{ +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +impl super::DistString for Uniform { fn append_string(&self, rng: &mut R, string: &mut alloc::string::String, len: usize) { // Getting the hi value to assume the required length to reserve in string. let mut hi = self.0.sampler.low + self.0.sampler.range - 1; @@ -884,11 +890,15 @@ pub struct UniformFloat { } macro_rules! uniform_float_impl { - ($ty:ty, $uty:ident, $f_scalar:ident, $u_scalar:ident, $bits_to_discard:expr) => { + ($($meta:meta)?, $ty:ty, $uty:ident, $f_scalar:ident, $u_scalar:ident, $bits_to_discard:expr) => { + $(#[cfg($meta)] + #[cfg_attr(doc_cfg, doc(cfg($meta)))])? impl SampleUniform for $ty { type Sampler = UniformFloat<$ty>; } + $(#[cfg($meta)] + #[cfg_attr(doc_cfg, doc(cfg($meta)))])? impl UniformSampler for UniformFloat<$ty> { type X = $ty; @@ -1088,24 +1098,24 @@ macro_rules! uniform_float_impl { }; } -uniform_float_impl! { f32, u32, f32, u32, 32 - 23 } -uniform_float_impl! { f64, u64, f64, u64, 64 - 52 } +uniform_float_impl! { , f32, u32, f32, u32, 32 - 23 } +uniform_float_impl! { , f64, u64, f64, u64, 64 - 52 } #[cfg(feature = "simd_support")] -uniform_float_impl! { f32x2, u32x2, f32, u32, 32 - 23 } +uniform_float_impl! { feature = "simd_support", f32x2, u32x2, f32, u32, 32 - 23 } #[cfg(feature = "simd_support")] -uniform_float_impl! { f32x4, u32x4, f32, u32, 32 - 23 } +uniform_float_impl! { feature = "simd_support", f32x4, u32x4, f32, u32, 32 - 23 } #[cfg(feature = "simd_support")] -uniform_float_impl! { f32x8, u32x8, f32, u32, 32 - 23 } +uniform_float_impl! { feature = "simd_support", f32x8, u32x8, f32, u32, 32 - 23 } #[cfg(feature = "simd_support")] -uniform_float_impl! { f32x16, u32x16, f32, u32, 32 - 23 } +uniform_float_impl! { feature = "simd_support", f32x16, u32x16, f32, u32, 32 - 23 } #[cfg(feature = "simd_support")] -uniform_float_impl! { f64x2, u64x2, f64, u64, 64 - 52 } +uniform_float_impl! { feature = "simd_support", f64x2, u64x2, f64, u64, 64 - 52 } #[cfg(feature = "simd_support")] -uniform_float_impl! { f64x4, u64x4, f64, u64, 64 - 52 } +uniform_float_impl! { feature = "simd_support", f64x4, u64x4, f64, u64, 64 - 52 } #[cfg(feature = "simd_support")] -uniform_float_impl! { f64x8, u64x8, f64, u64, 64 - 52 } +uniform_float_impl! { feature = "simd_support", f64x8, u64x8, f64, u64, 64 - 52 } /// The back-end implementing [`UniformSampler`] for `Duration`. diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 908021ad3dc..8d19e57df21 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -637,17 +637,17 @@ impl SliceRandom for [T] { // for an unbiased permutation. // It ensures that the last `amount` elements of the slice // are randomly selected from the whole slice. - - //`IncreasingUniform::next_index()` is faster than `gen_index` - //but only works for 32 bit integers - //So we must use the slow method if the slice is longer than that. + + // `IncreasingUniform::next_index()` is faster than `gen_index` + // but only works for 32 bit integers + // So we must use the slow method if the slice is longer than that. if self.len() < (u32::MAX as usize) { let mut chooser = IncreasingUniform::new(rng, m as u32); for i in m..self.len() { let index = chooser.next_index(); self.swap(i, index); } - } else { + } else { for i in m..self.len() { let index = gen_index(rng, i + 1); self.swap(i, index); @@ -674,6 +674,7 @@ pub struct SliceChooseIter<'a, S: ?Sized + 'a, T: 'a> { } #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl<'a, S: Index + ?Sized + 'a, T: 'a> Iterator for SliceChooseIter<'a, S, T> { type Item = &'a T; @@ -688,6 +689,7 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> Iterator for SliceCho } #[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator for SliceChooseIter<'a, S, T> { @@ -701,7 +703,6 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator // platforms. #[inline] fn gen_index(rng: &mut R, ubound: usize) -> usize { - if ubound <= (core::u32::MAX as usize) { rng.gen_range(0..ubound as u32) as usize } else { From be75bf5b903b75ac186d66bc8658c387c11a409b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 09:27:47 +0000 Subject: [PATCH 350/443] Update zerocopy requirement from =0.8.0-alpha.5 to =0.8.0-alpha.6 (#1401) --- Cargo.toml | 2 +- rand_core/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e9d4a1173a..55a819e48d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ rand_core = { path = "rand_core", version = "=0.9.0-alpha.0", default-features = log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.0", default-features = false, optional = true } -zerocopy = { version = "=0.8.0-alpha.5", default-features = false, features = ["simd"] } +zerocopy = { version = "=0.8.0-alpha.6", default-features = false, features = ["simd"] } [target.'cfg(unix)'.dependencies] # Used for fork protection (reseeding.rs) diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 28845f3d661..624f182d5ac 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -32,4 +32,4 @@ serde1 = ["serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } getrandom = { version = "0.2", optional = true } -zerocopy = { version = "=0.8.0-alpha.5", default-features = false } +zerocopy = { version = "=0.8.0-alpha.6", default-features = false } From b45e8920849e9ae3a5c06d8d9404678a2107e664 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 09:28:20 +0000 Subject: [PATCH 351/443] Update average requirement from 0.13 to 0.14 (#1388) --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 36533d46464..33efde13682 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -37,6 +37,6 @@ rand_pcg = { version = "=0.9.0-alpha.0", path = "../rand_pcg" } # For inline examples rand = { path = "..", version = "=0.9.0-alpha.0", features = ["small_rng"] } # Histogram implementation for testing uniformity -average = { version = "0.13", features = [ "std" ] } +average = { version = "0.14", features = [ "std" ] } # Special functions for testing distributions special = "0.10.3" From 4cbbb340ad2942197b1d38c9dc314a99bdef4609 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 18 Mar 2024 17:41:15 +0000 Subject: [PATCH 352/443] Remove automatic (delayed) reseed-on-fork (#1379) * benches/generators.rs: standardize thread_rng benchmarks * Remove cfgs from examples * Remove ReadRng * Add ThreadRng::reseed and doc to use * Remove fork protection from ReseedingRng; remove libc dep * Enable ReseedingRng without std * Move ReseedingRng up; remove module rand::rngs::adapter --- CHANGELOG.md | 4 + Cargo.toml | 8 +- SECURITY.md | 3 - benches/generators.rs | 26 +---- examples/monte-carlo.rs | 3 - examples/monty-hall.rs | 2 - examples/rayon-monte-carlo.rs | 2 - src/rngs/adapter/mod.rs | 16 --- src/rngs/adapter/read.rs | 150 ---------------------------- src/rngs/mod.rs | 4 +- src/rngs/{adapter => }/reseeding.rs | 107 ++------------------ src/rngs/std.rs | 2 +- src/rngs/thread.rs | 37 +++++-- 13 files changed, 53 insertions(+), 311 deletions(-) delete mode 100644 src/rngs/adapter/mod.rs delete mode 100644 src/rngs/adapter/read.rs rename src/rngs/{adapter => }/reseeding.rs (70%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1251c69db1f..abd5ed81e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [0.9.1] - unreleased - Add the `Slice::num_choices` method to the Slice distribution (#1402) +### Generators +- `ReseedingRng::reseed` also resets the random data cache. +- Remove fork-protection from `ReseedingRng` and `ThreadRng`. Instead, it is recommended to call `ThreadRng::reseed` on fork. + ## [0.9.0-alpha.0] - 2024-02-18 This is a pre-release. To depend on this version, use `rand = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/Cargo.toml b/Cargo.toml index 55a819e48d6..67340e121e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] # To build locally: # RUSTDOCFLAGS="--cfg doc_cfg -Zunstable-options --generate-link-to-definition" cargo +nightly doc --all --all-features --no-deps --open all-features = true -rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"] +rustdoc-args = ["--cfg", "doc_cfg", "-Zunstable-options", "--generate-link-to-definition"] [package.metadata.playground] features = ["small_rng", "serde1"] @@ -34,7 +34,7 @@ serde1 = ["serde", "rand_core/serde1"] # Option (enabled by default): without "std" rand uses libcore; this option # enables functionality expected to be available on a standard platform. -std = ["rand_core/std", "rand_chacha?/std", "alloc", "libc"] +std = ["rand_core/std", "rand_chacha?/std", "alloc"] # Option: "alloc" enables support for Vec and Box when not using "std" alloc = ["rand_core/alloc"] @@ -71,10 +71,6 @@ serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.0", default-features = false, optional = true } zerocopy = { version = "=0.8.0-alpha.6", default-features = false, features = ["simd"] } -[target.'cfg(unix)'.dependencies] -# Used for fork protection (reseeding.rs) -libc = { version = "0.2.22", optional = true, default-features = false } - [dev-dependencies] rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.0" } # Only to test serde1 diff --git a/SECURITY.md b/SECURITY.md index a31b4e23fd3..a48752976c7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,9 +23,6 @@ are expected to provide the following: For some RNGs, notably `OsRng`, `ThreadRng` and those wrapped by `ReseedingRng`, we provide limited mitigations against side-channel attacks: -- After a process fork on Unix, there is an upper-bound on the number of bits - output by the RNG before the processes diverge, after which outputs from - each process's RNG are uncorrelated - After the state (memory) of an RNG is leaked, there is an upper-bound on the number of bits of output by the RNG before prediction of output by an observer again becomes computationally-infeasible diff --git a/benches/generators.rs b/benches/generators.rs index 12b8460f0b5..9d3012fccbe 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -18,7 +18,7 @@ use core::mem::size_of; use test::{black_box, Bencher}; use rand::prelude::*; -use rand::rngs::adapter::ReseedingRng; +use rand::rngs::ReseedingRng; use rand::rngs::{mock::StepRng, OsRng}; use rand_chacha::{ChaCha12Rng, ChaCha20Core, ChaCha20Rng, ChaCha8Rng}; use rand_pcg::{Pcg32, Pcg64, Pcg64Mcg, Pcg64Dxsm}; @@ -52,6 +52,7 @@ gen_bytes!(gen_bytes_std, StdRng::from_entropy()); #[cfg(feature = "small_rng")] gen_bytes!(gen_bytes_small, SmallRng::from_thread_rng()); gen_bytes!(gen_bytes_os, OsRng); +gen_bytes!(gen_bytes_thread, thread_rng()); macro_rules! gen_uint { ($fnn:ident, $ty:ty, $gen:expr) => { @@ -82,6 +83,7 @@ gen_uint!(gen_u32_std, u32, StdRng::from_entropy()); #[cfg(feature = "small_rng")] gen_uint!(gen_u32_small, u32, SmallRng::from_thread_rng()); gen_uint!(gen_u32_os, u32, OsRng); +gen_uint!(gen_u32_thread, u32, thread_rng()); gen_uint!(gen_u64_step, u64, StepRng::new(0, 1)); gen_uint!(gen_u64_pcg32, u64, Pcg32::from_entropy()); @@ -95,6 +97,7 @@ gen_uint!(gen_u64_std, u64, StdRng::from_entropy()); #[cfg(feature = "small_rng")] gen_uint!(gen_u64_small, u64, SmallRng::from_thread_rng()); gen_uint!(gen_u64_os, u64, OsRng); +gen_uint!(gen_u64_thread, u64, thread_rng()); macro_rules! init_gen { ($fnn:ident, $gen:ident) => { @@ -141,24 +144,3 @@ reseeding_bytes!(reseeding_chacha20_32k, 32); reseeding_bytes!(reseeding_chacha20_64k, 64); reseeding_bytes!(reseeding_chacha20_256k, 256); reseeding_bytes!(reseeding_chacha20_1M, 1024); - - -macro_rules! threadrng_uint { - ($fnn:ident, $ty:ty) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = thread_rng(); - b.iter(|| { - let mut accum: $ty = 0; - for _ in 0..RAND_BENCH_N { - accum = accum.wrapping_add(rng.gen::<$ty>()); - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -threadrng_uint!(thread_rng_u32, u32); -threadrng_uint!(thread_rng_u64, u64); diff --git a/examples/monte-carlo.rs b/examples/monte-carlo.rs index a72cc1e9f47..21ab109ce05 100644 --- a/examples/monte-carlo.rs +++ b/examples/monte-carlo.rs @@ -23,9 +23,6 @@ //! We can use the above fact to estimate the value of π: pick many points in //! the square at random, calculate the fraction that fall within the circle, //! and multiply this fraction by 4. - -#![cfg(all(feature = "std", feature = "std_rng"))] - use rand::distributions::{Distribution, Uniform}; fn main() { diff --git a/examples/monty-hall.rs b/examples/monty-hall.rs index 7499193bcea..bb4bd870007 100644 --- a/examples/monty-hall.rs +++ b/examples/monty-hall.rs @@ -26,8 +26,6 @@ //! //! [Monty Hall Problem]: https://en.wikipedia.org/wiki/Monty_Hall_problem -#![cfg(all(feature = "std", feature = "std_rng"))] - use rand::distributions::{Distribution, Uniform}; use rand::Rng; diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index 7e703c01d2d..839a1593a1b 100644 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -38,8 +38,6 @@ //! over BATCH_SIZE trials. Manually batching also turns out to be faster //! for the nondeterministic version of this program as well. -#![cfg(all(feature = "std", feature = "std_rng"))] - use rand::distributions::{Distribution, Uniform}; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; use rayon::prelude::*; diff --git a/src/rngs/adapter/mod.rs b/src/rngs/adapter/mod.rs deleted file mode 100644 index bd1d2943233..00000000000 --- a/src/rngs/adapter/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Wrappers / adapters forming RNGs - -mod read; -mod reseeding; - -#[allow(deprecated)] -pub use self::read::{ReadError, ReadRng}; -pub use self::reseeding::ReseedingRng; diff --git a/src/rngs/adapter/read.rs b/src/rngs/adapter/read.rs deleted file mode 100644 index 25a9ca7fca4..00000000000 --- a/src/rngs/adapter/read.rs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// Copyright 2013 The Rust Project Developers. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! A wrapper around any Read to treat it as an RNG. - -#![allow(deprecated)] - -use std::fmt; -use std::io::Read; - -use rand_core::{impls, Error, RngCore}; - - -/// An RNG that reads random bytes straight from any type supporting -/// [`std::io::Read`], for example files. -/// -/// This will work best with an infinite reader, but that is not required. -/// -/// This can be used with `/dev/urandom` on Unix but it is recommended to use -/// [`OsRng`] instead. -/// -/// # Panics -/// -/// `ReadRng` uses [`std::io::Read::read_exact`], which retries on interrupts. -/// All other errors from the underlying reader, including when it does not -/// have enough data, will only be reported through [`try_fill_bytes`]. -/// The other [`RngCore`] methods will panic in case of an error. -/// -/// [`OsRng`]: crate::rngs::OsRng -/// [`try_fill_bytes`]: RngCore::try_fill_bytes -#[derive(Debug)] -#[deprecated(since="0.8.4", note="removal due to lack of usage")] -pub struct ReadRng { - reader: R, -} - -impl ReadRng { - /// Create a new `ReadRng` from a `Read`. - pub fn new(r: R) -> ReadRng { - ReadRng { reader: r } - } -} - -impl RngCore for ReadRng { - fn next_u32(&mut self) -> u32 { - impls::next_u32_via_fill(self) - } - - fn next_u64(&mut self) -> u64 { - impls::next_u64_via_fill(self) - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.try_fill_bytes(dest).unwrap_or_else(|err| { - panic!( - "reading random bytes from Read implementation failed; error: {}", - err - ) - }); - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - if dest.is_empty() { - return Ok(()); - } - // Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`. - self.reader - .read_exact(dest) - .map_err(|e| Error::new(ReadError(e))) - } -} - -/// `ReadRng` error type -#[derive(Debug)] -#[deprecated(since="0.8.4")] -pub struct ReadError(std::io::Error); - -impl fmt::Display for ReadError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ReadError: {}", self.0) - } -} - -impl std::error::Error for ReadError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.0) - } -} - - -#[cfg(test)] -mod test { - use std::println; - - use super::ReadRng; - use crate::RngCore; - - #[test] - fn test_reader_rng_u64() { - // transmute from the target to avoid endianness concerns. - #[rustfmt::skip] - let v = [0u8, 0, 0, 0, 0, 0, 0, 1, - 0, 4, 0, 0, 3, 0, 0, 2, - 5, 0, 0, 0, 0, 0, 0, 0]; - let mut rng = ReadRng::new(&v[..]); - - assert_eq!(rng.next_u64(), 1 << 56); - assert_eq!(rng.next_u64(), (2 << 56) + (3 << 32) + (4 << 8)); - assert_eq!(rng.next_u64(), 5); - } - - #[test] - fn test_reader_rng_u32() { - let v = [0u8, 0, 0, 1, 0, 0, 2, 0, 3, 0, 0, 0]; - let mut rng = ReadRng::new(&v[..]); - - assert_eq!(rng.next_u32(), 1 << 24); - assert_eq!(rng.next_u32(), 2 << 16); - assert_eq!(rng.next_u32(), 3); - } - - #[test] - fn test_reader_rng_fill_bytes() { - let v = [1u8, 2, 3, 4, 5, 6, 7, 8]; - let mut w = [0u8; 8]; - - let mut rng = ReadRng::new(&v[..]); - rng.fill_bytes(&mut w); - - assert!(v == w); - } - - #[test] - fn test_reader_rng_insufficient_bytes() { - let v = [1u8, 2, 3, 4, 5, 6, 7, 8]; - let mut w = [0u8; 9]; - - let mut rng = ReadRng::new(&v[..]); - - let result = rng.try_fill_bytes(&mut w); - assert!(result.is_err()); - println!("Error: {}", result.unwrap_err()); - } -} diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index 9013c57d10c..9caeb09e028 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -96,8 +96,8 @@ //! [`rand_xoshiro`]: https://crates.io/crates/rand_xoshiro //! [`rng` tag]: https://crates.io/keywords/rng -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] -#[cfg(feature = "std")] pub mod adapter; +mod reseeding; +pub use reseeding::ReseedingRng; pub mod mock; // Public so we don't export `StepRng` directly, making it a bit // more clear it is intended for testing. diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/reseeding.rs similarity index 70% rename from src/rngs/adapter/reseeding.rs rename to src/rngs/reseeding.rs index 39ddfd71047..ff3436a477b 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -22,10 +22,6 @@ use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// /// - On a manual call to [`reseed()`]. /// - After `clone()`, the clone will be reseeded on first use. -/// - When a process is forked on UNIX, the RNGs in both the parent and child -/// processes will be reseeded just before the next call to -/// [`BlockRngCore::generate`], i.e. "soon". For ChaCha and Hc128, this is a -/// maximum of 63 and 15, respectively, `u32` values before reseeding. /// - After the PRNG has generated a configurable number of random bytes. /// /// # When should reseeding after a fixed number of generated bytes be used? @@ -43,12 +39,6 @@ use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// Use [`ReseedingRng::new`] with a `threshold` of `0` to disable reseeding /// after a fixed number of generated bytes. /// -/// # Limitations -/// -/// It is recommended that a `ReseedingRng` (including `ThreadRng`) not be used -/// from a fork handler. -/// Use `OsRng` or `getrandom`, or defer your use of the RNG until later. -/// /// # Error handling /// /// Although unlikely, reseeding the wrapped PRNG can fail. `ReseedingRng` will @@ -67,7 +57,7 @@ use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; /// use rand_chacha::ChaCha20Core; // Internal part of ChaChaRng that /// // implements BlockRngCore /// use rand::rngs::OsRng; -/// use rand::rngs::adapter::ReseedingRng; +/// use rand::rngs::ReseedingRng; /// /// let prng = ChaCha20Core::from_entropy(); /// let mut reseeding_rng = ReseedingRng::new(prng, 0, OsRng); @@ -102,8 +92,11 @@ where ReseedingRng(BlockRng::new(ReseedingCore::new(rng, threshold, reseeder))) } - /// Reseed the internal PRNG. + /// Immediately reseed the generator + /// + /// This discards any remaining random data in the cache. pub fn reseed(&mut self) -> Result<(), Error> { + self.0.reset(); self.0.core.reseed() } } @@ -158,7 +151,6 @@ struct ReseedingCore { reseeder: Rsdr, threshold: i64, bytes_until_reseed: i64, - fork_counter: usize, } impl BlockRngCore for ReseedingCore @@ -170,12 +162,11 @@ where type Results = ::Results; fn generate(&mut self, results: &mut Self::Results) { - let global_fork_counter = fork::get_fork_counter(); - if self.bytes_until_reseed <= 0 || self.is_forked(global_fork_counter) { + if self.bytes_until_reseed <= 0 { // We get better performance by not calling only `reseed` here // and continuing with the rest of the function, but by directly // returning from a non-inlined function. - return self.reseed_and_generate(results, global_fork_counter); + return self.reseed_and_generate(results); } let num_bytes = size_of_val(results.as_ref()); self.bytes_until_reseed -= num_bytes as i64; @@ -191,7 +182,6 @@ where /// Create a new `ReseedingCore`. fn new(rng: R, threshold: u64, reseeder: Rsdr) -> Self { use ::core::i64::MAX; - fork::register_fork_handler(); // Because generating more values than `i64::MAX` takes centuries on // current hardware, we just clamp to that value. @@ -210,7 +200,6 @@ where reseeder, threshold, bytes_until_reseed: threshold, - fork_counter: 0, } } @@ -222,30 +211,9 @@ where }) } - fn is_forked(&self, global_fork_counter: usize) -> bool { - // In theory, on 32-bit platforms, it is possible for - // `global_fork_counter` to wrap around after ~4e9 forks. - // - // This check will detect a fork in the normal case where - // `fork_counter < global_fork_counter`, and also when the difference - // between both is greater than `isize::MAX` (wrapped around). - // - // It will still fail to detect a fork if there have been more than - // `isize::MAX` forks, without any reseed in between. Seems unlikely - // enough. - (self.fork_counter.wrapping_sub(global_fork_counter) as isize) < 0 - } - #[inline(never)] - fn reseed_and_generate( - &mut self, results: &mut ::Results, global_fork_counter: usize, - ) { - #![allow(clippy::if_same_then_else)] // false positive - if self.is_forked(global_fork_counter) { - info!("Fork detected, reseeding RNG"); - } else { - trace!("Reseeding RNG (periodic reseed)"); - } + fn reseed_and_generate(&mut self, results: &mut ::Results) { + trace!("Reseeding RNG (periodic reseed)"); let num_bytes = size_of_val(results.as_ref()); @@ -253,7 +221,6 @@ where warn!("Reseeding RNG failed: {}", e); let _ = e; } - self.fork_counter = global_fork_counter; self.bytes_until_reseed = self.threshold - num_bytes as i64; self.inner.generate(results); @@ -271,7 +238,6 @@ where reseeder: self.reseeder.clone(), threshold: self.threshold, bytes_until_reseed: 0, // reseed clone on first use - fork_counter: self.fork_counter, } } } @@ -283,61 +249,6 @@ where { } - -#[cfg(all(unix, not(target_os = "emscripten")))] -mod fork { - use core::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::Once; - - // Fork protection - // - // We implement fork protection on Unix using `pthread_atfork`. - // When the process is forked, we increment `RESEEDING_RNG_FORK_COUNTER`. - // Every `ReseedingRng` stores the last known value of the static in - // `fork_counter`. If the cached `fork_counter` is less than - // `RESEEDING_RNG_FORK_COUNTER`, it is time to reseed this RNG. - // - // If reseeding fails, we don't deal with this by setting a delay, but just - // don't update `fork_counter`, so a reseed is attempted as soon as - // possible. - - static RESEEDING_RNG_FORK_COUNTER: AtomicUsize = AtomicUsize::new(0); - - pub fn get_fork_counter() -> usize { - RESEEDING_RNG_FORK_COUNTER.load(Ordering::Relaxed) - } - - extern "C" fn fork_handler() { - // Note: fetch_add is defined to wrap on overflow - // (which is what we want). - RESEEDING_RNG_FORK_COUNTER.fetch_add(1, Ordering::Relaxed); - } - - pub fn register_fork_handler() { - static REGISTER: Once = Once::new(); - REGISTER.call_once(|| { - // Bump the counter before and after forking (see #1169): - let ret = unsafe { libc::pthread_atfork( - Some(fork_handler), - Some(fork_handler), - Some(fork_handler), - ) }; - if ret != 0 { - panic!("libc::pthread_atfork failed with code {}", ret); - } - }); - } -} - -#[cfg(not(all(unix, not(target_os = "emscripten"))))] -mod fork { - pub fn get_fork_counter() -> usize { - 0 - } - pub fn register_fork_handler() {} -} - - #[cfg(feature = "std_rng")] #[cfg(test)] mod test { diff --git a/src/rngs/std.rs b/src/rngs/std.rs index 31b20a2dc5d..3de125f3e5c 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -10,7 +10,7 @@ use crate::{CryptoRng, Error, RngCore, SeedableRng}; -#[cfg(feature = "getrandom")] +#[cfg(any(test, feature = "getrandom"))] pub(crate) use rand_chacha::ChaCha12Core as Core; use rand_chacha::ChaCha12Rng as Rng; diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 6c8d83c02eb..449d6d6cf48 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -14,7 +14,7 @@ use std::thread_local; use std::fmt; use super::std::Core; -use crate::rngs::adapter::ReseedingRng; +use crate::rngs::ReseedingRng; use crate::rngs::OsRng; use crate::{CryptoRng, Error, RngCore, SeedableRng}; @@ -42,8 +42,6 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// /// This type is a reference to a lazily-initialized thread-local generator. /// An instance can be obtained via [`thread_rng`] or via `ThreadRng::default()`. -/// This handle is safe to use everywhere (including thread-local destructors), -/// though it is recommended not to use inside a fork handler. /// The handle cannot be passed between threads (is not `Send` or `Sync`). /// /// `ThreadRng` uses the same CSPRNG as [`StdRng`], ChaCha12. As with @@ -51,8 +49,23 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// of security and performance. /// /// `ThreadRng` is automatically seeded from [`OsRng`] with periodic reseeding -/// (every 64 kiB, as well as "soon" after a fork on Unix — see [`ReseedingRng`] -/// documentation for details). +/// (every 64 kiB — see [`ReseedingRng`] documentation for details). +/// +/// `ThreadRng` is not automatically reseeded on fork. It is recommended to +/// explicitly call [`ThreadRng::reseed`] immediately after a fork, for example: +/// ```ignore +/// fn do_fork() { +/// let pid = unsafe { libc::fork() }; +/// if pid == 0 { +/// // Reseed ThreadRng in child processes: +/// rand::thread_rng().reseed(); +/// } +/// } +/// ``` +/// +/// Methods on `ThreadRng` are not reentrant-safe and thus should not be called +/// from an interrupt (e.g. a fork handler) unless it can be guaranteed that no +/// other method on the same `ThreadRng` is currently executing. /// /// Security must be considered relative to a threat model and validation /// requirements. `ThreadRng` attempts to meet basic security considerations @@ -61,7 +74,7 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// leaking internal secrets e.g. via [`Debug`] implementation or serialization. /// Memory is not zeroized on drop. /// -/// [`ReseedingRng`]: crate::rngs::adapter::ReseedingRng +/// [`ReseedingRng`]: crate::rngs::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng #[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))))] #[derive(Clone)] @@ -70,6 +83,18 @@ pub struct ThreadRng { rng: Rc>>, } +impl ThreadRng { + /// Immediately reseed the generator + /// + /// This discards any remaining random data in the cache. + pub fn reseed(&mut self) -> Result<(), Error> { + // SAFETY: We must make sure to stop using `rng` before anyone else + // creates another mutable reference + let rng = unsafe { &mut *self.rng.get() }; + rng.reseed() + } +} + /// Debug implementation does not leak internal state impl fmt::Debug for ThreadRng { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { From 1015e709dad7933f69f563b77f152ecc5ed0f690 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 18 Mar 2024 19:08:37 +0000 Subject: [PATCH 353/443] Prepare 0.9.0-alpha.1 (#1413) --- CHANGELOG.md | 2 +- Cargo.toml | 8 ++++---- rand_chacha/CHANGELOG.md | 2 ++ rand_chacha/Cargo.toml | 4 ++-- rand_core/CHANGELOG.md | 2 ++ rand_core/Cargo.toml | 2 +- rand_distr/CHANGELOG.md | 3 +++ rand_distr/Cargo.toml | 8 ++++---- rand_pcg/CHANGELOG.md | 2 ++ rand_pcg/Cargo.toml | 4 ++-- 10 files changed, 23 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abd5ed81e68..6d0c9f5f2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. -## [0.9.1] - unreleased +## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) ### Generators diff --git a/Cargo.toml b/Cargo.toml index 67340e121e8..9e420a72981 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.9.0-alpha.0" +version = "0.9.0-alpha.1" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -65,14 +65,14 @@ members = [ ] [dependencies] -rand_core = { path = "rand_core", version = "=0.9.0-alpha.0", default-features = false } +rand_core = { path = "rand_core", version = "=0.9.0-alpha.1", default-features = false } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } -rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.0", default-features = false, optional = true } +rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.1", default-features = false, optional = true } zerocopy = { version = "=0.8.0-alpha.6", default-features = false, features = ["simd"] } [dev-dependencies] -rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.0" } +rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index dcc9d2e688c..d74dc1f9b0e 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0-alpha.1] - 2024-03-18 + ## [0.9.0-alpha.0] - 2024-02-18 This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index bcc09f61cc0..e5330a2c3ca 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_chacha" -version = "0.9.0-alpha.0" +version = "0.9.0-alpha.1" authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -19,7 +19,7 @@ rust-version = "1.60" rustdoc-args = ["--generate-link-to-definition"] [dependencies] -rand_core = { path = "../rand_core", version = "=0.9.0-alpha.0" } +rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1" } ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index b7f00895622..80810a025bd 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0-alpha.1] - 2024-03-18 + ## [0.9.0-alpha.0] - 2024-02-18 This is a pre-release. To depend on this version, use `rand_core = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 624f182d5ac..4dc4a09cf63 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.9.0-alpha.0" +version = "0.9.0-alpha.1" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 24b43648ebd..e29c59910df 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0-alpha.1] - 2024-03-18 +- Target `rand` version `0.9.0-alpha.1` + ## [0.5.0-alpha.0] - 2024-02-18 This is a pre-release. To depend on this version, use `rand_distr = "=0.5.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 33efde13682..b60bb7cec75 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.5.0-alpha.0" +version = "0.5.0-alpha.1" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -27,15 +27,15 @@ std_math = ["num-traits/std"] serde1 = ["serde", "rand/serde1"] [dependencies] -rand = { path = "..", version = "=0.9.0-alpha.0", default-features = false } +rand = { path = "..", version = "=0.9.0-alpha.1", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } serde_with = { version = "3.6.1", optional = true } [dev-dependencies] -rand_pcg = { version = "=0.9.0-alpha.0", path = "../rand_pcg" } +rand_pcg = { version = "=0.9.0-alpha.1", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "=0.9.0-alpha.0", features = ["small_rng"] } +rand = { path = "..", version = "=0.9.0-alpha.1", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.14", features = [ "std" ] } # Special functions for testing distributions diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index c60386cf0bc..18ea4229732 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0-alpha.1] - 2024-03-18 + ## [0.9.0-alpha.0] - 2024-02-18 This is a pre-release. To depend on this version, use `rand_pcg = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 1fa514344bf..80383b97781 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_pcg" -version = "0.9.0-alpha.0" +version = "0.9.0-alpha.1" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -22,7 +22,7 @@ rustdoc-args = ["--generate-link-to-definition"] serde1 = ["serde"] [dependencies] -rand_core = { path = "../rand_core", version = "=0.9.0-alpha.0" } +rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] From a4579072555e52bf43b8f3e952152782fc88bfb1 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 22 Mar 2024 07:23:16 +0000 Subject: [PATCH 354/443] Use Simd::wrapping_neg (#1414) --- src/distributions/uniform.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index e69d5fe0200..d1b65519353 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -701,21 +701,15 @@ macro_rules! uniform_simd_int_impl { if !(low.simd_le(high).all()) { return Err(Error::EmptyRange); } - let unsigned_max = Simd::splat(::core::$unsigned::MAX); // NOTE: all `Simd` operations are inherently wrapping, // see https://doc.rust-lang.org/std/simd/struct.Simd.html let range: Simd<$unsigned, LANES> = ((high - low) + Simd::splat(1)).cast(); - // `% 0` will panic at runtime. + + // We must avoid divide-by-zero by using 0 % 1 == 0. let not_full_range = range.simd_gt(Simd::splat(0)); - // replacing 0 with `unsigned_max` allows a faster `select` - // with bitwise OR - let modulo = not_full_range.select(range, unsigned_max); - // wrapping addition - // TODO: replace with `range.wrapping_neg() % module` when Simd supports this. - let ints_to_reject = (Simd::splat(0) - range) % modulo; - // When `range` is 0, `lo` of `v.wmul(range)` will always be - // zero which means only one sample is needed. + let modulo = not_full_range.select(range, Simd::splat(1)); + let ints_to_reject = range.wrapping_neg() % modulo; Ok(UniformInt { low, From 2eaf8545d4c829a9a69943fdf1ceec2db1b3ec1d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 22 Mar 2024 12:33:33 +0000 Subject: [PATCH 355/443] Bump the MSRV to 1.61.0 (#1416) --- .github/workflows/test.yml | 2 +- CHANGELOG.md | 3 + Cargo.lock.msrv | 603 ++++++++++++++++++++++++++-------- Cargo.toml | 2 +- README.md | 4 +- rand_chacha/Cargo.toml | 2 +- rand_chacha/README.md | 2 +- rand_core/CHANGELOG.md | 3 + rand_core/Cargo.toml | 2 +- rand_core/README.md | 2 +- rand_distr/Cargo.toml | 2 +- rand_distr/README.md | 2 +- rand_distr/benches/Cargo.toml | 2 +- rand_pcg/Cargo.toml | 2 +- rand_pcg/README.md | 2 +- 15 files changed, 490 insertions(+), 145 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a4e860aec0..a1c5b39b618 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,7 +47,7 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu variant: MSRV - toolchain: 1.60.0 + toolchain: 1.61.0 - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d0c9f5f2d8..9c9ec9859ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [Unreleased] +- Bump the MSRV to 1.61.0 + ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/Cargo.lock.msrv b/Cargo.lock.msrv index b173eb6d866..0a197d12e04 100644 --- a/Cargo.lock.msrv +++ b/Cargo.lock.msrv @@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[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 = "anes" version = "0.1.6" @@ -14,7 +38,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -27,15 +51,21 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "average" -version = "0.13.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843ec791d3f24503bbf72bbd5e49a3ab4dbb4bcd0a8ef6b0c908efa73caa27b1" +checksum = "6d804c74bb2d66e9b7047658d21af0f1c937d7d2466410cbf1aed3b0c04048d4" dependencies = [ "easy-cast", "float-ord", "num-traits", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "bincode" version = "1.3.3" @@ -53,9 +83,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "cast" @@ -63,17 +93,36 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + [[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.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + [[package]] name = "ciborium" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -82,15 +131,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -98,25 +147,31 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.5" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53da17d37dba964b9b3ecb5c5a1f193a2762c700e6829201e645b9381c99dc7" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags", "clap_lex", - "indexmap", + "indexmap 1.9.3", "textwrap", ] [[package]] name = "clap_lex" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "criterion" version = "0.4.0" @@ -155,61 +210,109 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ - "cfg-if", + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", ] [[package]] name = "easy-cast" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd102ee8c418348759919b83b81cdbdc933ffe29740b903df448b4bafaa348e" +checksum = "10936778145f3bea71fd9bf61332cce28c28e96a380714f7ab34838b80733fd6" dependencies = [ "libm", ] [[package]] name = "either" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "float-ord" @@ -217,11 +320,17 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -230,9 +339,12 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] [[package]] name = "hashbrown" @@ -240,6 +352,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -249,14 +367,67 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[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 = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", ] [[package]] @@ -270,15 +441,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -291,39 +462,39 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.138" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] -name = "memoffset" -version = "0.7.1" +name = "memchr" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -331,19 +502,19 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -353,15 +524,15 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "os_str_bytes" -version = "6.1.0" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "plotters" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" dependencies = [ "num-traits", "plotters-backend", @@ -372,19 +543,25 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" [[package]] name = "plotters-svg" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" dependencies = [ "plotters-backend", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -393,40 +570,40 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.9.0" +version = "0.9.0-alpha.1" dependencies = [ "bincode", "criterion", - "libc", "log", "rand_chacha", "rand_core", "rand_pcg", "rayon", "serde", + "zerocopy", ] [[package]] name = "rand_chacha" -version = "0.4.0" +version = "0.9.0-alpha.1" dependencies = [ "ppv-lite86", "rand_core", @@ -436,27 +613,29 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.7.0" +version = "0.9.0-alpha.1" dependencies = [ "getrandom", "serde", + "zerocopy", ] [[package]] name = "rand_distr" -version = "0.5.0" +version = "0.5.0-alpha.1" dependencies = [ "average", "num-traits", "rand", "rand_pcg", "serde", + "serde_with", "special", ] [[package]] name = "rand_pcg" -version = "0.4.0" +version = "0.9.0-alpha.1" dependencies = [ "bincode", "rand_core", @@ -475,9 +654,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -487,24 +666,38 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -515,26 +708,20 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "serde" -version = "1.0.149" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", @@ -543,29 +730,65 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_with" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.5", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "special" -version = "0.8.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a65e074159b75dcf173a4733ab2188baac24967b5c8ec9ed87ae15fcbc7636" +checksum = "b89cf0d71ae639fdd8097350bfac415a41aabf1d5ddd356295fdc95f09760382" dependencies = [ - "libc", + "libm", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" -version = "1.0.105" +version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", @@ -574,9 +797,40 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.2" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] [[package]] name = "tinytemplate" @@ -590,18 +844,17 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -613,9 +866,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -623,9 +876,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -638,9 +891,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -648,9 +901,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -661,15 +914,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -693,9 +946,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -705,3 +958,89 @@ 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-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "zerocopy" +version = "0.8.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db678a6ee512bd06adf35c35be471cae2f9c82a5aed2b5d15e03628c98bddd57" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201585ea96d37ee69f2ac769925ca57160cef31acb137c16f38b02b76f4c1e62" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 9e420a72981..6c7ce73f66a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["random", "rng"] categories = ["algorithms", "no-std"] autobenches = true edition = "2021" -rust-version = "1.60" +rust-version = "1.61" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] diff --git a/README.md b/README.md index 30c697922bf..e6430e83771 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A Rust library for random number generation, featuring: @@ -97,7 +97,7 @@ issue tracker with the keyword `yank` *should* uncover the motivation. ### Rust version requirements -The Minimum Supported Rust Version (MSRV) is `rustc >= 1.60.0`. +The Minimum Supported Rust Version (MSRV) is `rustc >= 1.61.0`. Older releases may work (depending on feature configuration) but are untested. ## Crate Features diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index e5330a2c3ca..0162599ce74 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -13,7 +13,7 @@ ChaCha random number generator keywords = ["random", "rng", "chacha"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.60" +rust-version = "1.61" [package.metadata.docs.rs] rustdoc-args = ["--generate-link-to-definition"] diff --git a/rand_chacha/README.md b/rand_chacha/README.md index 0fd1b64c0d4..16922525681 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) [![API](https://docs.rs/rand_chacha/badge.svg)](https://docs.rs/rand_chacha) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A cryptographically secure random number generator that uses the ChaCha algorithm. diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index 80810a025bd..ec29912baa0 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +- Bump the MSRV to 1.61.0 + ## [0.9.0-alpha.1] - 2024-03-18 ## [0.9.0-alpha.0] - 2024-02-18 diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 4dc4a09cf63..642e9a12ad6 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -13,7 +13,7 @@ Core random number generator traits and tools for implementation. keywords = ["random", "rng"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.60" +rust-version = "1.61" [package.metadata.docs.rs] # To build locally: diff --git a/rand_core/README.md b/rand_core/README.md index a08f7c99251..3edcd4a2f95 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_core) [![API](https://docs.rs/rand_core/badge.svg)](https://docs.rs/rand_core) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Core traits and error types of the [rand] library, plus tools for implementing RNGs. diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index b60bb7cec75..917c307fee8 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -13,7 +13,7 @@ Sampling from random number distributions keywords = ["random", "rng", "distribution", "probability"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.60" +rust-version = "1.61" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] diff --git a/rand_distr/README.md b/rand_distr/README.md index 016e8981d85..8b43ba9e9a6 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a full suite of random number distribution sampling routines. diff --git a/rand_distr/benches/Cargo.toml b/rand_distr/benches/Cargo.toml index 2dd82c7973a..e9c41c31540 100644 --- a/rand_distr/benches/Cargo.toml +++ b/rand_distr/benches/Cargo.toml @@ -5,7 +5,7 @@ authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" description = "Criterion benchmarks of the rand_distr crate" edition = "2021" -rust-version = "1.60" +rust-version = "1.61" publish = false [workspace] diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 80383b97781..e80e16b086a 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -13,7 +13,7 @@ Selected PCG random number generators keywords = ["random", "rng", "pcg"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.60" +rust-version = "1.61" [package.metadata.docs.rs] rustdoc-args = ["--generate-link-to-definition"] diff --git a/rand_pcg/README.md b/rand_pcg/README.md index da1a1beeffb..8a4efbeb815 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -5,7 +5,7 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_pcg) [![API](https://docs.rs/rand_pcg/badge.svg)](https://docs.rs/rand_pcg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.60+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a selection of PCG random number generators. From 0e626c74366d3622120231ebf6451509051a4435 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 25 Mar 2024 14:56:17 +0000 Subject: [PATCH 356/443] Cleaner README, remove MSRV shields (#1417) --- README.md | 49 +++++++++---------------------------------- rand_chacha/README.md | 1 - rand_core/README.md | 1 - rand_distr/README.md | 1 - rand_pcg/README.md | 1 - 5 files changed, 10 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index e6430e83771..ab080dfc050 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A Rust library for random number generation, featuring: @@ -56,49 +55,15 @@ rand = "0.8.5" To get started using Rand, see [The Book](https://rust-random.github.io/book). - ## Versions Rand is *mature* (suitable for general usage, with infrequent breaking releases -which minimise breakage) but not yet at 1.0. We maintain compatibility with -pinned versions of the Rust compiler (see below). - -Current Rand versions are: +which minimise breakage) but not yet at 1.0. Current versions are: -- Version 0.7 was released in June 2019, moving most non-uniform distributions - to an external crate, moving `from_entropy` to `SeedableRng`, and many small - changes and fixes. - Version 0.8 was released in December 2020 with many small changes. +- Version 0.9 is in development with many small changes. -A detailed [changelog](CHANGELOG.md) is available for releases. - -When upgrading to the next minor series (especially 0.4 → 0.5), we recommend -reading the [Upgrade Guide](https://rust-random.github.io/book/update.html). - -Rand has not yet reached 1.0 implying some breaking changes may arrive in the -future ([SemVer](https://semver.org/) allows each 0.x.0 release to include -breaking changes), but is considered *mature*: breaking changes are minimised -and breaking releases are infrequent. - -Rand libs have inter-dependencies and make use of the -[semver trick](https://github.com/dtolnay/semver-trick/) in order to make traits -compatible across crate versions. (This is especially important for `RngCore` -and `SeedableRng`.) A few crate releases are thus compatibility shims, -depending on the *next* lib version (e.g. `rand_core` versions `0.2.2` and -`0.3.1`). This means, for example, that `rand_core_0_4_0::SeedableRng` and -`rand_core_0_3_0::SeedableRng` are distinct, incompatible traits, which can -cause build errors. Usually, running `cargo update` is enough to fix any issues. - -### Yanked versions - -Some versions of Rand crates have been yanked ("unreleased"). Where this occurs, -the crate's CHANGELOG *should* be updated with a rationale, and a search on the -issue tracker with the keyword `yank` *should* uncover the motivation. - -### Rust version requirements - -The Minimum Supported Rust Version (MSRV) is `rustc >= 1.61.0`. -Older releases may work (depending on feature configuration) but are untested. +See the [CHANGELOG](CHANGELOG.md) or [Upgrade Guide](https://rust-random.github.io/book/update.html) for more details. ## Crate Features @@ -113,7 +78,7 @@ Rand is built with these features enabled by default: Optionally, the following dependencies can be enabled: -- `log` enables logging via the `log` crate +- `log` enables logging via [log](https://crates.io/crates/log) Additionally, these features configure Rand: @@ -132,6 +97,12 @@ unavailable (unless `getrandom` is enabled), large parts of `seq` are unavailable (unless `alloc` is enabled), and `thread_rng` and `random` are unavailable. +## Portability and platform support + +Many (but not all) algorithms are intended to have reproducible output. Read more in the book: [Portability](https://rust-random.github.io/book/portability.html). + +The Rand library supports a variety of CPU architectures. Platform integration is outsourced to [getrandom](https://docs.rs/getrandom/latest/getrandom/). + ### WASM support Seeding entropy from OS on WASM target `wasm32-unknown-unknown` is not diff --git a/rand_chacha/README.md b/rand_chacha/README.md index 16922525681..6412538eadb 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -5,7 +5,6 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) [![API](https://docs.rs/rand_chacha/badge.svg)](https://docs.rs/rand_chacha) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) A cryptographically secure random number generator that uses the ChaCha algorithm. diff --git a/rand_core/README.md b/rand_core/README.md index 3edcd4a2f95..31c742d9f3e 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -5,7 +5,6 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_core) [![API](https://docs.rs/rand_core/badge.svg)](https://docs.rs/rand_core) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Core traits and error types of the [rand] library, plus tools for implementing RNGs. diff --git a/rand_distr/README.md b/rand_distr/README.md index 8b43ba9e9a6..f61e1b13d60 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -5,7 +5,6 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) [![API](https://docs.rs/rand_distr/badge.svg)](https://docs.rs/rand_distr) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a full suite of random number distribution sampling routines. diff --git a/rand_pcg/README.md b/rand_pcg/README.md index 8a4efbeb815..cb6f096492a 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -5,7 +5,6 @@ [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_pcg) [![API](https://docs.rs/rand_pcg/badge.svg)](https://docs.rs/rand_pcg) -[![Minimum rustc version](https://img.shields.io/badge/rustc-1.61+-lightgray.svg)](https://github.com/rust-random/rand#rust-version-requirements) Implements a selection of PCG random number generators. From 05189751b56f0e46794b95428b1a43566a197a6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:45:29 +0100 Subject: [PATCH 357/443] Update average requirement from 0.14 to 0.15 (#1426) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- rand_distr/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 917c307fee8..49663743020 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -37,6 +37,6 @@ rand_pcg = { version = "=0.9.0-alpha.1", path = "../rand_pcg" } # For inline examples rand = { path = "..", version = "=0.9.0-alpha.1", features = ["small_rng"] } # Histogram implementation for testing uniformity -average = { version = "0.14", features = [ "std" ] } +average = { version = "0.15", features = [ "std" ] } # Special functions for testing distributions special = "0.10.3" From 7b37c15cb7462daddf519ce6ef05e7d873a2ed9d Mon Sep 17 00:00:00 2001 From: Michael Dyer <59163924+MichaelOwenDyer@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:47:17 +0200 Subject: [PATCH 358/443] Add methods weight, weights, and total_weight to weighted_index.rs (#1420) --- CHANGELOG.md | 1 + src/distributions/weighted_index.rs | 188 ++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c9ec9859ec..9019ae4b8b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. ## [Unreleased] +- Add `rand::distributions::WeightedIndex::{weight, weights, total_weight}` (#1420) - Bump the MSRV to 1.61.0 ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 59ff1c1915d..dfb30ef62a4 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -15,6 +15,7 @@ use core::fmt; // Note that this whole module is only imported if feature="alloc" is enabled. use alloc::vec::Vec; +use core::fmt::Debug; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; @@ -243,6 +244,124 @@ impl WeightedIndex { } } +/// A lazy-loading iterator over the weights of a `WeightedIndex` distribution. +/// This is returned by [`WeightedIndex::weights`]. +pub struct WeightedIndexIter<'a, X: SampleUniform + PartialOrd> { + weighted_index: &'a WeightedIndex, + index: usize, +} + +impl<'a, X> Debug for WeightedIndexIter<'a, X> + where + X: SampleUniform + PartialOrd + Debug, + X::Sampler: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("WeightedIndexIter") + .field("weighted_index", &self.weighted_index) + .field("index", &self.index) + .finish() + } +} + +impl<'a, X> Clone for WeightedIndexIter<'a, X> +where + X: SampleUniform + PartialOrd, +{ + fn clone(&self) -> Self { + WeightedIndexIter { + weighted_index: self.weighted_index, + index: self.index, + } + } +} + +impl<'a, X> Iterator for WeightedIndexIter<'a, X> +where + X: for<'b> ::core::ops::SubAssign<&'b X> + + SampleUniform + + PartialOrd + + Clone, +{ + type Item = X; + + fn next(&mut self) -> Option { + match self.weighted_index.weight(self.index) { + None => None, + Some(weight) => { + self.index += 1; + Some(weight) + } + } + } +} + +impl WeightedIndex { + /// Returns the weight at the given index, if it exists. + /// + /// If the index is out of bounds, this will return `None`. + /// + /// # Example + /// + /// ``` + /// use rand::distributions::WeightedIndex; + /// + /// let weights = [0, 1, 2]; + /// let dist = WeightedIndex::new(&weights).unwrap(); + /// assert_eq!(dist.weight(0), Some(0)); + /// assert_eq!(dist.weight(1), Some(1)); + /// assert_eq!(dist.weight(2), Some(2)); + /// assert_eq!(dist.weight(3), None); + /// ``` + pub fn weight(&self, index: usize) -> Option + where + X: for<'a> ::core::ops::SubAssign<&'a X> + { + let mut weight = if index < self.cumulative_weights.len() { + self.cumulative_weights[index].clone() + } else if index == self.cumulative_weights.len() { + self.total_weight.clone() + } else { + return None; + }; + if index > 0 { + weight -= &self.cumulative_weights[index - 1]; + } + Some(weight) + } + + /// Returns a lazy-loading iterator containing the current weights of this distribution. + /// + /// If this distribution has not been updated since its creation, this will return the + /// same weights as were passed to `new`. + /// + /// # Example + /// + /// ``` + /// use rand::distributions::WeightedIndex; + /// + /// let weights = [1, 2, 3]; + /// let mut dist = WeightedIndex::new(&weights).unwrap(); + /// assert_eq!(dist.weights().collect::>(), vec![1, 2, 3]); + /// dist.update_weights(&[(0, &2)]).unwrap(); + /// assert_eq!(dist.weights().collect::>(), vec![2, 2, 3]); + /// ``` + pub fn weights(&self) -> WeightedIndexIter<'_, X> + where + X: for<'a> ::core::ops::SubAssign<&'a X> + { + WeightedIndexIter { + weighted_index: self, + index: 0, + } + } + + /// Returns the sum of all weights in this distribution. + pub fn total_weight(&self) -> X { + self.total_weight.clone() + } +} + impl Distribution for WeightedIndex where X: SampleUniform + PartialOrd, @@ -458,6 +577,75 @@ mod test { } } + #[test] + fn test_update_weights_errors() { + let data = [ + ( + &[1i32, 0, 0][..], + &[(0, &0)][..], + WeightError::InsufficientNonZero, + ), + ( + &[10, 10, 10, 10][..], + &[(1, &-11)][..], + WeightError::InvalidWeight, // A weight is negative + ), + ( + &[1, 2, 3, 4, 5][..], + &[(1, &5), (0, &5)][..], // Wrong order + WeightError::InvalidInput, + ), + ( + &[1][..], + &[(1, &1)][..], // Index too large + WeightError::InvalidInput, + ), + ]; + + for (weights, update, err) in data.iter() { + let total_weight = weights.iter().sum::(); + let mut distr = WeightedIndex::new(weights.to_vec()).unwrap(); + assert_eq!(distr.total_weight, total_weight); + match distr.update_weights(update) { + Ok(_) => panic!("Expected update_weights to fail, but it succeeded"), + Err(e) => assert_eq!(e, *err), + } + } + } + + #[test] + fn test_weight_at() { + let data = [ + &[1][..], + &[10, 2, 3, 4][..], + &[1, 2, 3, 0, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7][..], + &[u32::MAX][..], + ]; + + for weights in data.iter() { + let distr = WeightedIndex::new(weights.to_vec()).unwrap(); + for (i, weight) in weights.iter().enumerate() { + assert_eq!(distr.weight(i), Some(*weight)); + } + assert_eq!(distr.weight(weights.len()), None); + } + } + + #[test] + fn test_weights() { + let data = [ + &[1][..], + &[10, 2, 3, 4][..], + &[1, 2, 3, 0, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7][..], + &[u32::MAX][..], + ]; + + for weights in data.iter() { + let distr = WeightedIndex::new(weights.to_vec()).unwrap(); + assert_eq!(distr.weights().collect::>(), weights.to_vec()); + } + } + #[test] fn value_stability() { fn test_samples( From 2f6fee9ca3e8383df703fc2df2722aec547fb791 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:48:45 +0100 Subject: [PATCH 359/443] Bump actions/configure-pages from 4 to 5 (#1427) --- .github/workflows/gh-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 6c78ff56baf..3b8f20b7143 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -35,7 +35,7 @@ jobs: rm target/doc/.lock - name: Setup Pages - uses: actions/configure-pages@v4 + uses: actions/configure-pages@v5 - name: Upload artifact uses: actions/upload-pages-artifact@v3 From 2f14aec3f4ad928d108011f2a2f43b05e27abf5a Mon Sep 17 00:00:00 2001 From: Michael Dyer Date: Wed, 3 Apr 2024 19:16:18 +0200 Subject: [PATCH 360/443] Put macro `x86_intrinsic_impl` behind x86 configuration guard --- src/distributions/integer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index 8b9ae4ad403..d7bb988d1be 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -23,7 +23,6 @@ use core::num::{ NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize,NonZeroI128 }; #[cfg(feature = "simd_support")] use core::simd::*; -use core::mem; impl Distribution for Standard { #[inline] @@ -123,6 +122,7 @@ impl_nzint!(NonZeroI64, NonZeroI64::new); impl_nzint!(NonZeroI128, NonZeroI128::new); impl_nzint!(NonZeroIsize, NonZeroIsize::new); +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] macro_rules! x86_intrinsic_impl { ($meta:meta, $($intrinsic:ident),+) => {$( #[cfg($meta)] @@ -132,7 +132,7 @@ macro_rules! x86_intrinsic_impl { fn sample(&self, rng: &mut R) -> $intrinsic { // On proper hardware, this should compile to SIMD instructions // Verified on x86 Haswell with __m128i, __m256i - let mut buf = [0_u8; mem::size_of::<$intrinsic>()]; + let mut buf = [0_u8; core::mem::size_of::<$intrinsic>()]; rng.fill_bytes(&mut buf); // x86 is little endian so no need for conversion zerocopy::transmute!(buf) From bca078e929bdf2a1e1730cf394e497e3d0732605 Mon Sep 17 00:00:00 2001 From: Michael Dyer <59163924+MichaelOwenDyer@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:04:30 +0200 Subject: [PATCH 361/443] Optimize imports (#1430) --- benches/distributions.rs | 46 ++++++++++++++--------------- benches/misc.rs | 14 ++++----- benches/seq.rs | 2 +- rand_distr/src/binomial.rs | 4 +-- rand_distr/src/cauchy.rs | 2 +- rand_distr/src/frechet.rs | 12 ++++---- rand_distr/src/geometric.rs | 10 +++---- rand_distr/src/gumbel.rs | 8 ++--- rand_distr/src/normal.rs | 2 +- rand_distr/src/skew_normal.rs | 34 ++++++++++----------- rand_distr/src/utils.rs | 2 +- rand_distr/src/weibull.rs | 2 +- rand_distr/src/weighted_alias.rs | 20 ++++++------- rand_distr/src/zipf.rs | 8 ++--- rand_distr/tests/pdf.rs | 8 ++--- rand_distr/tests/sparkline.rs | 4 +-- rand_distr/tests/value_stability.rs | 4 +-- src/distributions/bernoulli.rs | 4 +-- src/distributions/distribution.rs | 10 +++---- src/distributions/float.rs | 4 +-- src/distributions/uniform.rs | 10 +++---- src/distributions/weighted_index.rs | 18 +++++------ src/rngs/reseeding.rs | 18 +++++------ src/seq/index.rs | 6 ++-- src/seq/mod.rs | 24 +++++++-------- 25 files changed, 137 insertions(+), 139 deletions(-) diff --git a/benches/distributions.rs b/benches/distributions.rs index f637fe4ae47..6b6bd39735a 100644 --- a/benches/distributions.rs +++ b/benches/distributions.rs @@ -101,7 +101,7 @@ macro_rules! distr_duration { let x: Duration = distr.sample(&mut rng); accum = accum .checked_add(x) - .unwrap_or(Duration::new(u64::max_value(), 999_999_999)); + .unwrap_or(Duration::new(u64::MAX, 999_999_999)); } accum }); @@ -145,10 +145,10 @@ distr_int!(distr_uniform_isize, isize, Uniform::new(-1060478432isize, 1858574057 distr_float!(distr_uniform_f32, f32, Uniform::new(2.26f32, 2.319).unwrap()); distr_float!(distr_uniform_f64, f64, Uniform::new(2.26f64, 2.319).unwrap()); -const LARGE_SEC: u64 = u64::max_value() / 1000; +const LARGE_SEC: u64 = u64::MAX / 1000; distr_duration!(distr_uniform_duration_largest, - Uniform::new_inclusive(Duration::new(0, 0), Duration::new(u64::max_value(), 999_999_999)).unwrap() + Uniform::new_inclusive(Duration::new(0, 0), Duration::new(u64::MAX, 999_999_999)).unwrap() ); distr_duration!(distr_uniform_duration_large, Uniform::new(Duration::new(0, 0), Duration::new(LARGE_SEC, 1_000_000_000 / 2)).unwrap() @@ -332,26 +332,26 @@ macro_rules! uniform_single { // (32769) will only reject 32769 / 4294967296 samples. const HALF_16_BIT_UNSIGNED: u16 = 1 << 15; -uniform_sample!(uniform_u16x1_allm1_new, u16, 0, u16::max_value(), 1); +uniform_sample!(uniform_u16x1_allm1_new, u16, 0, u16::MAX, 1); uniform_sample!(uniform_u16x1_halfp1_new, u16, 0, HALF_16_BIT_UNSIGNED + 1, 1); uniform_sample!(uniform_u16x1_half_new, u16, 0, HALF_16_BIT_UNSIGNED, 1); uniform_sample!(uniform_u16x1_halfm1_new, u16, 0, HALF_16_BIT_UNSIGNED - 1, 1); uniform_sample!(uniform_u16x1_6_new, u16, 0, 6u16, 1); -uniform_single!(uniform_u16x1_allm1_single, u16, 0, u16::max_value(), 1); +uniform_single!(uniform_u16x1_allm1_single, u16, 0, u16::MAX, 1); uniform_single!(uniform_u16x1_halfp1_single, u16, 0, HALF_16_BIT_UNSIGNED + 1, 1); uniform_single!(uniform_u16x1_half_single, u16, 0, HALF_16_BIT_UNSIGNED, 1); uniform_single!(uniform_u16x1_halfm1_single, u16, 0, HALF_16_BIT_UNSIGNED - 1, 1); uniform_single!(uniform_u16x1_6_single, u16, 0, 6u16, 1); -uniform_inclusive!(uniform_u16x10_all_new_inclusive, u16, 0, u16::max_value(), 10); -uniform_sample!(uniform_u16x10_allm1_new, u16, 0, u16::max_value(), 10); +uniform_inclusive!(uniform_u16x10_all_new_inclusive, u16, 0, u16::MAX, 10); +uniform_sample!(uniform_u16x10_allm1_new, u16, 0, u16::MAX, 10); uniform_sample!(uniform_u16x10_halfp1_new, u16, 0, HALF_16_BIT_UNSIGNED + 1, 10); uniform_sample!(uniform_u16x10_half_new, u16, 0, HALF_16_BIT_UNSIGNED, 10); uniform_sample!(uniform_u16x10_halfm1_new, u16, 0, HALF_16_BIT_UNSIGNED - 1, 10); uniform_sample!(uniform_u16x10_6_new, u16, 0, 6u16, 10); -uniform_single!(uniform_u16x10_allm1_single, u16, 0, u16::max_value(), 10); +uniform_single!(uniform_u16x10_allm1_single, u16, 0, u16::MAX, 10); uniform_single!(uniform_u16x10_halfp1_single, u16, 0, HALF_16_BIT_UNSIGNED + 1, 10); uniform_single!(uniform_u16x10_half_single, u16, 0, HALF_16_BIT_UNSIGNED, 10); uniform_single!(uniform_u16x10_halfm1_single, u16, 0, HALF_16_BIT_UNSIGNED - 1, 10); @@ -360,26 +360,26 @@ uniform_single!(uniform_u16x10_6_single, u16, 0, 6u16, 10); const HALF_32_BIT_UNSIGNED: u32 = 1 << 31; -uniform_sample!(uniform_u32x1_allm1_new, u32, 0, u32::max_value(), 1); +uniform_sample!(uniform_u32x1_allm1_new, u32, 0, u32::MAX, 1); uniform_sample!(uniform_u32x1_halfp1_new, u32, 0, HALF_32_BIT_UNSIGNED + 1, 1); uniform_sample!(uniform_u32x1_half_new, u32, 0, HALF_32_BIT_UNSIGNED, 1); uniform_sample!(uniform_u32x1_halfm1_new, u32, 0, HALF_32_BIT_UNSIGNED - 1, 1); uniform_sample!(uniform_u32x1_6_new, u32, 0, 6u32, 1); -uniform_single!(uniform_u32x1_allm1_single, u32, 0, u32::max_value(), 1); +uniform_single!(uniform_u32x1_allm1_single, u32, 0, u32::MAX, 1); uniform_single!(uniform_u32x1_halfp1_single, u32, 0, HALF_32_BIT_UNSIGNED + 1, 1); uniform_single!(uniform_u32x1_half_single, u32, 0, HALF_32_BIT_UNSIGNED, 1); uniform_single!(uniform_u32x1_halfm1_single, u32, 0, HALF_32_BIT_UNSIGNED - 1, 1); uniform_single!(uniform_u32x1_6_single, u32, 0, 6u32, 1); -uniform_inclusive!(uniform_u32x10_all_new_inclusive, u32, 0, u32::max_value(), 10); -uniform_sample!(uniform_u32x10_allm1_new, u32, 0, u32::max_value(), 10); +uniform_inclusive!(uniform_u32x10_all_new_inclusive, u32, 0, u32::MAX, 10); +uniform_sample!(uniform_u32x10_allm1_new, u32, 0, u32::MAX, 10); uniform_sample!(uniform_u32x10_halfp1_new, u32, 0, HALF_32_BIT_UNSIGNED + 1, 10); uniform_sample!(uniform_u32x10_half_new, u32, 0, HALF_32_BIT_UNSIGNED, 10); uniform_sample!(uniform_u32x10_halfm1_new, u32, 0, HALF_32_BIT_UNSIGNED - 1, 10); uniform_sample!(uniform_u32x10_6_new, u32, 0, 6u32, 10); -uniform_single!(uniform_u32x10_allm1_single, u32, 0, u32::max_value(), 10); +uniform_single!(uniform_u32x10_allm1_single, u32, 0, u32::MAX, 10); uniform_single!(uniform_u32x10_halfp1_single, u32, 0, HALF_32_BIT_UNSIGNED + 1, 10); uniform_single!(uniform_u32x10_half_single, u32, 0, HALF_32_BIT_UNSIGNED, 10); uniform_single!(uniform_u32x10_halfm1_single, u32, 0, HALF_32_BIT_UNSIGNED - 1, 10); @@ -387,26 +387,26 @@ uniform_single!(uniform_u32x10_6_single, u32, 0, 6u32, 10); const HALF_64_BIT_UNSIGNED: u64 = 1 << 63; -uniform_sample!(uniform_u64x1_allm1_new, u64, 0, u64::max_value(), 1); +uniform_sample!(uniform_u64x1_allm1_new, u64, 0, u64::MAX, 1); uniform_sample!(uniform_u64x1_halfp1_new, u64, 0, HALF_64_BIT_UNSIGNED + 1, 1); uniform_sample!(uniform_u64x1_half_new, u64, 0, HALF_64_BIT_UNSIGNED, 1); uniform_sample!(uniform_u64x1_halfm1_new, u64, 0, HALF_64_BIT_UNSIGNED - 1, 1); uniform_sample!(uniform_u64x1_6_new, u64, 0, 6u64, 1); -uniform_single!(uniform_u64x1_allm1_single, u64, 0, u64::max_value(), 1); +uniform_single!(uniform_u64x1_allm1_single, u64, 0, u64::MAX, 1); uniform_single!(uniform_u64x1_halfp1_single, u64, 0, HALF_64_BIT_UNSIGNED + 1, 1); uniform_single!(uniform_u64x1_half_single, u64, 0, HALF_64_BIT_UNSIGNED, 1); uniform_single!(uniform_u64x1_halfm1_single, u64, 0, HALF_64_BIT_UNSIGNED - 1, 1); uniform_single!(uniform_u64x1_6_single, u64, 0, 6u64, 1); -uniform_inclusive!(uniform_u64x10_all_new_inclusive, u64, 0, u64::max_value(), 10); -uniform_sample!(uniform_u64x10_allm1_new, u64, 0, u64::max_value(), 10); +uniform_inclusive!(uniform_u64x10_all_new_inclusive, u64, 0, u64::MAX, 10); +uniform_sample!(uniform_u64x10_allm1_new, u64, 0, u64::MAX, 10); uniform_sample!(uniform_u64x10_halfp1_new, u64, 0, HALF_64_BIT_UNSIGNED + 1, 10); uniform_sample!(uniform_u64x10_half_new, u64, 0, HALF_64_BIT_UNSIGNED, 10); uniform_sample!(uniform_u64x10_halfm1_new, u64, 0, HALF_64_BIT_UNSIGNED - 1, 10); uniform_sample!(uniform_u64x10_6_new, u64, 0, 6u64, 10); -uniform_single!(uniform_u64x10_allm1_single, u64, 0, u64::max_value(), 10); +uniform_single!(uniform_u64x10_allm1_single, u64, 0, u64::MAX, 10); uniform_single!(uniform_u64x10_halfp1_single, u64, 0, HALF_64_BIT_UNSIGNED + 1, 10); uniform_single!(uniform_u64x10_half_single, u64, 0, HALF_64_BIT_UNSIGNED, 10); uniform_single!(uniform_u64x10_halfm1_single, u64, 0, HALF_64_BIT_UNSIGNED - 1, 10); @@ -414,26 +414,26 @@ uniform_single!(uniform_u64x10_6_single, u64, 0, 6u64, 10); const HALF_128_BIT_UNSIGNED: u128 = 1 << 127; -uniform_sample!(uniform_u128x1_allm1_new, u128, 0, u128::max_value(), 1); +uniform_sample!(uniform_u128x1_allm1_new, u128, 0, u128::MAX, 1); uniform_sample!(uniform_u128x1_halfp1_new, u128, 0, HALF_128_BIT_UNSIGNED + 1, 1); uniform_sample!(uniform_u128x1_half_new, u128, 0, HALF_128_BIT_UNSIGNED, 1); uniform_sample!(uniform_u128x1_halfm1_new, u128, 0, HALF_128_BIT_UNSIGNED - 1, 1); uniform_sample!(uniform_u128x1_6_new, u128, 0, 6u128, 1); -uniform_single!(uniform_u128x1_allm1_single, u128, 0, u128::max_value(), 1); +uniform_single!(uniform_u128x1_allm1_single, u128, 0, u128::MAX, 1); uniform_single!(uniform_u128x1_halfp1_single, u128, 0, HALF_128_BIT_UNSIGNED + 1, 1); uniform_single!(uniform_u128x1_half_single, u128, 0, HALF_128_BIT_UNSIGNED, 1); uniform_single!(uniform_u128x1_halfm1_single, u128, 0, HALF_128_BIT_UNSIGNED - 1, 1); uniform_single!(uniform_u128x1_6_single, u128, 0, 6u128, 1); -uniform_inclusive!(uniform_u128x10_all_new_inclusive, u128, 0, u128::max_value(), 10); -uniform_sample!(uniform_u128x10_allm1_new, u128, 0, u128::max_value(), 10); +uniform_inclusive!(uniform_u128x10_all_new_inclusive, u128, 0, u128::MAX, 10); +uniform_sample!(uniform_u128x10_allm1_new, u128, 0, u128::MAX, 10); uniform_sample!(uniform_u128x10_halfp1_new, u128, 0, HALF_128_BIT_UNSIGNED + 1, 10); uniform_sample!(uniform_u128x10_half_new, u128, 0, HALF_128_BIT_UNSIGNED, 10); uniform_sample!(uniform_u128x10_halfm1_new, u128, 0, HALF_128_BIT_UNSIGNED - 1, 10); uniform_sample!(uniform_u128x10_6_new, u128, 0, 6u128, 10); -uniform_single!(uniform_u128x10_allm1_single, u128, 0, u128::max_value(), 10); +uniform_single!(uniform_u128x10_allm1_single, u128, 0, u128::MAX, 10); uniform_single!(uniform_u128x10_halfp1_single, u128, 0, HALF_128_BIT_UNSIGNED + 1, 10); uniform_single!(uniform_u128x10_half_single, u128, 0, HALF_128_BIT_UNSIGNED, 10); uniform_single!(uniform_u128x10_halfm1_single, u128, 0, HALF_128_BIT_UNSIGNED - 1, 10); diff --git a/benches/misc.rs b/benches/misc.rs index f0b761f99ed..a3aef17c57e 100644 --- a/benches/misc.rs +++ b/benches/misc.rs @@ -23,7 +23,7 @@ fn misc_gen_bool_const(b: &mut Bencher) { let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { let mut accum = true; - for _ in 0..crate::RAND_BENCH_N { + for _ in 0..RAND_BENCH_N { accum ^= rng.gen_bool(0.18); } accum @@ -36,7 +36,7 @@ fn misc_gen_bool_var(b: &mut Bencher) { b.iter(|| { let mut accum = true; let mut p = 0.18; - for _ in 0..crate::RAND_BENCH_N { + for _ in 0..RAND_BENCH_N { accum ^= rng.gen_bool(p); p += 0.0001; } @@ -49,7 +49,7 @@ fn misc_gen_ratio_const(b: &mut Bencher) { let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { let mut accum = true; - for _ in 0..crate::RAND_BENCH_N { + for _ in 0..RAND_BENCH_N { accum ^= rng.gen_ratio(2, 3); } accum @@ -61,7 +61,7 @@ fn misc_gen_ratio_var(b: &mut Bencher) { let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { let mut accum = true; - for i in 2..(crate::RAND_BENCH_N as u32 + 2) { + for i in 2..(RAND_BENCH_N as u32 + 2) { accum ^= rng.gen_ratio(i, i + 1); } accum @@ -72,9 +72,9 @@ fn misc_gen_ratio_var(b: &mut Bencher) { fn misc_bernoulli_const(b: &mut Bencher) { let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { - let d = rand::distributions::Bernoulli::new(0.18).unwrap(); + let d = Bernoulli::new(0.18).unwrap(); let mut accum = true; - for _ in 0..crate::RAND_BENCH_N { + for _ in 0..RAND_BENCH_N { accum ^= rng.sample(d); } accum @@ -87,7 +87,7 @@ fn misc_bernoulli_var(b: &mut Bencher) { b.iter(|| { let mut accum = true; let mut p = 0.18; - for _ in 0..crate::RAND_BENCH_N { + for _ in 0..RAND_BENCH_N { let d = Bernoulli::new(p).unwrap(); accum ^= rng.sample(d); p += 0.0001; diff --git a/benches/seq.rs b/benches/seq.rs index 3d57d4872e6..bcc1e7c20e4 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -47,7 +47,7 @@ fn seq_slice_choose_1_of_1000(b: &mut Bencher) { } s }); - b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; + b.bytes = size_of::() as u64 * RAND_BENCH_N; } macro_rules! seq_slice_choose_multiple { diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 2d380c64688..1e8e5e190a3 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -77,7 +77,7 @@ impl Binomial { /// Convert a `f64` to an `i64`, panicking on overflow. fn f64_to_i64(x: f64) -> i64 { - assert!(x < (core::i64::MAX as f64)); + assert!(x < (i64::MAX as f64)); x as i64 } @@ -117,7 +117,7 @@ impl Distribution for Binomial { // When n*p < 10, so is n*p*q which is the variance, so a result > 110 would be 100 / sqrt(10) = 31 standard deviations away. const BINV_MAX_X : u64 = 110; - if (self.n as f64) * p < BINV_THRESHOLD && self.n <= (core::i32::MAX as u64) { + if (self.n as f64) * p < BINV_THRESHOLD && self.n <= (i32::MAX as u64) { // Use the BINV algorithm. let s = p / q; let a = ((self.n + 1) as f64) * s; diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index cd3e31b453f..fefaa737daf 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -137,7 +137,7 @@ mod test { #[test] fn value_stability() { - fn gen_samples(m: F, s: F, buf: &mut [F]) + fn gen_samples(m: F, s: F, buf: &mut [F]) where Standard: Distribution { let distr = Cauchy::new(m, s).unwrap(); let mut rng = crate::test::rng(353); diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index 63205b40cbd..781b7af1648 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -112,13 +112,13 @@ mod tests { #[test] #[should_panic] fn test_infinite_scale() { - Frechet::new(0.0, core::f64::INFINITY, 1.0).unwrap(); + Frechet::new(0.0, f64::INFINITY, 1.0).unwrap(); } #[test] #[should_panic] fn test_nan_scale() { - Frechet::new(0.0, core::f64::NAN, 1.0).unwrap(); + Frechet::new(0.0, f64::NAN, 1.0).unwrap(); } #[test] @@ -130,25 +130,25 @@ mod tests { #[test] #[should_panic] fn test_infinite_shape() { - Frechet::new(0.0, 1.0, core::f64::INFINITY).unwrap(); + Frechet::new(0.0, 1.0, f64::INFINITY).unwrap(); } #[test] #[should_panic] fn test_nan_shape() { - Frechet::new(0.0, 1.0, core::f64::NAN).unwrap(); + Frechet::new(0.0, 1.0, f64::NAN).unwrap(); } #[test] #[should_panic] fn test_infinite_location() { - Frechet::new(core::f64::INFINITY, 1.0, 1.0).unwrap(); + Frechet::new(f64::INFINITY, 1.0, 1.0).unwrap(); } #[test] #[should_panic] fn test_nan_location() { - Frechet::new(core::f64::NAN, 1.0, 1.0).unwrap(); + Frechet::new(f64::NAN, 1.0, 1.0).unwrap(); } #[test] diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 6ee64a77d98..5204013a5bd 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -94,7 +94,7 @@ impl Distribution for Geometric return failures; } - if self.p == 0.0 { return core::u64::MAX; } + if self.p == 0.0 { return u64::MAX; } let Geometric { p, pi, k } = *self; @@ -121,7 +121,7 @@ impl Distribution for Geometric // fewer iterations on average. ~ October 28, 2020 let m = loop { let m = rng.gen::() & ((1 << k) - 1); - let p_reject = if m <= core::i32::MAX as u64 { + let p_reject = if m <= i32::MAX as u64 { (1.0 - p).powi(m as i32) } else { (1.0 - p).powf(m as f64) @@ -176,9 +176,9 @@ mod test { #[test] fn test_geo_invalid_p() { - assert!(Geometric::new(core::f64::NAN).is_err()); - assert!(Geometric::new(core::f64::INFINITY).is_err()); - assert!(Geometric::new(core::f64::NEG_INFINITY).is_err()); + assert!(Geometric::new(f64::NAN).is_err()); + assert!(Geometric::new(f64::INFINITY).is_err()); + assert!(Geometric::new(f64::NEG_INFINITY).is_err()); assert!(Geometric::new(-0.5).is_err()); assert!(Geometric::new(0.0).is_ok()); diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index b254919f3b8..4a83658692a 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -101,25 +101,25 @@ mod tests { #[test] #[should_panic] fn test_infinite_scale() { - Gumbel::new(0.0, core::f64::INFINITY).unwrap(); + Gumbel::new(0.0, f64::INFINITY).unwrap(); } #[test] #[should_panic] fn test_nan_scale() { - Gumbel::new(0.0, core::f64::NAN).unwrap(); + Gumbel::new(0.0, f64::NAN).unwrap(); } #[test] #[should_panic] fn test_infinite_location() { - Gumbel::new(core::f64::INFINITY, 1.0).unwrap(); + Gumbel::new(f64::INFINITY, 1.0).unwrap(); } #[test] #[should_panic] fn test_nan_location() { - Gumbel::new(core::f64::NAN, 1.0).unwrap(); + Gumbel::new(f64::NAN, 1.0).unwrap(); } #[test] diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index b3b801dfed9..635f26f1d43 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -348,7 +348,7 @@ mod tests { #[test] fn test_log_normal_cv() { let lnorm = LogNormal::from_mean_cv(0.0, 0.0).unwrap(); - assert_eq!((lnorm.norm.mean, lnorm.norm.std_dev), (-core::f64::INFINITY, 0.0)); + assert_eq!((lnorm.norm.mean, lnorm.norm.std_dev), (f64::NEG_INFINITY, 0.0)); let lnorm = LogNormal::from_mean_cv(1.0, 0.0).unwrap(); assert_eq!((lnorm.norm.mean, lnorm.norm.std_dev), (0.0, 0.0)); diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 29ba413a0ac..3577147f863 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -150,7 +150,7 @@ where mod tests { use super::*; - fn test_samples>( + fn test_samples>( distr: D, zero: F, expected: &[F], ) { let mut rng = crate::test::rng(213); @@ -164,7 +164,7 @@ mod tests { #[test] #[should_panic] fn invalid_scale_nan() { - SkewNormal::new(0.0, core::f64::NAN, 0.0).unwrap(); + SkewNormal::new(0.0, f64::NAN, 0.0).unwrap(); } #[test] @@ -182,24 +182,24 @@ mod tests { #[test] #[should_panic] fn invalid_scale_infinite() { - SkewNormal::new(0.0, core::f64::INFINITY, 0.0).unwrap(); + SkewNormal::new(0.0, f64::INFINITY, 0.0).unwrap(); } #[test] #[should_panic] fn invalid_shape_nan() { - SkewNormal::new(0.0, 1.0, core::f64::NAN).unwrap(); + SkewNormal::new(0.0, 1.0, f64::NAN).unwrap(); } #[test] #[should_panic] fn invalid_shape_infinite() { - SkewNormal::new(0.0, 1.0, core::f64::INFINITY).unwrap(); + SkewNormal::new(0.0, 1.0, f64::INFINITY).unwrap(); } #[test] fn valid_location_nan() { - SkewNormal::new(core::f64::NAN, 1.0, 0.0).unwrap(); + SkewNormal::new(f64::NAN, 1.0, 0.0).unwrap(); } #[test] @@ -220,30 +220,30 @@ mod tests { ], ); test_samples( - SkewNormal::new(core::f64::INFINITY, 1.0, 0.0).unwrap(), + SkewNormal::new(f64::INFINITY, 1.0, 0.0).unwrap(), 0f64, &[ - core::f64::INFINITY, - core::f64::INFINITY, - core::f64::INFINITY, - core::f64::INFINITY, + f64::INFINITY, + f64::INFINITY, + f64::INFINITY, + f64::INFINITY, ], ); test_samples( - SkewNormal::new(core::f64::NEG_INFINITY, 1.0, 0.0).unwrap(), + SkewNormal::new(f64::NEG_INFINITY, 1.0, 0.0).unwrap(), 0f64, &[ - core::f64::NEG_INFINITY, - core::f64::NEG_INFINITY, - core::f64::NEG_INFINITY, - core::f64::NEG_INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, ], ); } #[test] fn skew_normal_value_location_nan() { - let skew_normal = SkewNormal::new(core::f64::NAN, 1.0, 0.0).unwrap(); + let skew_normal = SkewNormal::new(f64::NAN, 1.0, 0.0).unwrap(); let mut rng = crate::test::rng(213); let mut buf = [0.0; 4]; for x in &mut buf { diff --git a/rand_distr/src/utils.rs b/rand_distr/src/utils.rs index 4638e3623d2..052bfc49991 100644 --- a/rand_distr/src/utils.rs +++ b/rand_distr/src/utils.rs @@ -100,7 +100,7 @@ where (bits >> 12).into_float_with_exponent(1) - 3.0 } else { // Convert to a value in the range [1,2) and subtract to get (0,1) - (bits >> 12).into_float_with_exponent(0) - (1.0 - core::f64::EPSILON / 2.0) + (bits >> 12).into_float_with_exponent(0) - (1.0 - f64::EPSILON / 2.0) }; let x = u * x_tab[i]; diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index fe45eff6613..2ab74edde2c 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -105,7 +105,7 @@ mod tests { #[test] fn value_stability() { - fn test_samples>( + fn test_samples>( distr: D, zero: F, expected: &[F], ) { let mut rng = crate::test::rng(213); diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 236e2ad734b..0c07ead0df9 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -86,7 +86,7 @@ impl WeightedAliasIndex { /// - [`WeightError::InsufficientNonZero`] when the sum of all weights is zero. pub fn new(weights: Vec) -> Result { let n = weights.len(); - if n == 0 || n > ::core::u32::MAX as usize { + if n == 0 || n > u32::MAX as usize { return Err(WeightError::InvalidInput); } let n = n as u32; @@ -139,8 +139,8 @@ impl WeightedAliasIndex { fn new(size: u32) -> Self { Aliases { aliases: vec![0; size as usize].into_boxed_slice(), - smalls_head: ::core::u32::MAX, - bigs_head: ::core::u32::MAX, + smalls_head: u32::MAX, + bigs_head: u32::MAX, } } @@ -167,11 +167,11 @@ impl WeightedAliasIndex { } fn smalls_is_empty(&self) -> bool { - self.smalls_head == ::core::u32::MAX + self.smalls_head == u32::MAX } fn bigs_is_empty(&self) -> bool { - self.bigs_head == ::core::u32::MAX + self.bigs_head == u32::MAX } fn set_alias(&mut self, idx: u32, alias: u32) { @@ -378,7 +378,7 @@ mod test { // Floating point special cases assert_eq!( - WeightedAliasIndex::new(vec![::core::f32::INFINITY]).unwrap_err(), + WeightedAliasIndex::new(vec![f32::INFINITY]).unwrap_err(), WeightError::InvalidWeight ); assert_eq!( @@ -390,11 +390,11 @@ mod test { WeightError::InvalidWeight ); assert_eq!( - WeightedAliasIndex::new(vec![-::core::f32::INFINITY]).unwrap_err(), + WeightedAliasIndex::new(vec![f32::NEG_INFINITY]).unwrap_err(), WeightError::InvalidWeight ); assert_eq!( - WeightedAliasIndex::new(vec![::core::f32::NAN]).unwrap_err(), + WeightedAliasIndex::new(vec![f32::NAN]).unwrap_err(), WeightError::InvalidWeight ); } @@ -416,7 +416,7 @@ mod test { WeightError::InvalidWeight ); assert_eq!( - WeightedAliasIndex::new(vec![::core::i128::MIN]).unwrap_err(), + WeightedAliasIndex::new(vec![i128::MIN]).unwrap_err(), WeightError::InvalidWeight ); } @@ -438,7 +438,7 @@ mod test { WeightError::InvalidWeight ); assert_eq!( - WeightedAliasIndex::new(vec![::core::i8::MIN]).unwrap_err(), + WeightedAliasIndex::new(vec![i8::MIN]).unwrap_err(), WeightError::InvalidWeight ); } diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index e15b6cdd197..d0813ef9066 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -246,7 +246,7 @@ where F: Float, Standard: Distribution mod tests { use super::*; - fn test_samples>( + fn test_samples>( distr: D, zero: F, expected: &[F], ) { let mut rng = crate::test::rng(213); @@ -266,7 +266,7 @@ mod tests { #[test] #[should_panic] fn zeta_nan() { - Zeta::new(core::f64::NAN).unwrap(); + Zeta::new(f64::NAN).unwrap(); } #[test] @@ -316,7 +316,7 @@ mod tests { #[test] #[should_panic] fn zipf_nan() { - Zipf::new(10, core::f64::NAN).unwrap(); + Zipf::new(10, f64::NAN).unwrap(); } #[test] @@ -352,7 +352,7 @@ mod tests { #[test] fn zipf_sample_large_n() { - let d = Zipf::new(core::u64::MAX, 1.5).unwrap(); + let d = Zipf::new(u64::MAX, 1.5).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index b4fd7810926..be5ee0e2595 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -67,7 +67,7 @@ fn normal() { ); println!( "max diff: {:?}", - diff.iter().fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) + diff.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)) ); // Check that the differences are significantly smaller than the expected error. @@ -86,7 +86,7 @@ fn normal() { "max expected_error: {:?}", expected_error .iter() - .fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) + .fold(f64::NEG_INFINITY, |a, &b| a.max(b)) ); for (&d, &e) in diff.iter().zip(expected_error.iter()) { // Difference larger than 4 standard deviations or cutoff @@ -150,7 +150,7 @@ fn skew_normal() { ); println!( "max diff: {:?}", - diff.iter().fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) + diff.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)) ); // Check that the differences are significantly smaller than the expected error. @@ -169,7 +169,7 @@ fn skew_normal() { "max expected_error: {:?}", expected_error .iter() - .fold(core::f64::NEG_INFINITY, |a, &b| a.max(b)) + .fold(f64::NEG_INFINITY, |a, &b| a.max(b)) ); for (&d, &e) in diff.iter().zip(expected_error.iter()) { // Difference larger than 4 standard deviations or cutoff diff --git a/rand_distr/tests/sparkline.rs b/rand_distr/tests/sparkline.rs index 6ba48ba886e..ee6c9788d9c 100644 --- a/rand_distr/tests/sparkline.rs +++ b/rand_distr/tests/sparkline.rs @@ -71,9 +71,9 @@ pub fn render_f64(data: &[f64], buffer: &mut String) { assert!(x.is_finite(), "can only render finite values"); } let max = data.iter().fold( - core::f64::NEG_INFINITY, |a, &b| a.max(b)); + f64::NEG_INFINITY, |a, &b| a.max(b)); let min = data.iter().fold( - core::f64::INFINITY, |a, &b| a.min(b)); + f64::INFINITY, |a, &b| a.min(b)); let scale = ((N - 1) as f64) / (max - min); for x in data { let tick = ((x - min) * scale) as usize; diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index 88fe7d9ecab..7006dd0e816 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -11,7 +11,7 @@ use core::fmt::Debug; use rand::Rng; use rand_distr::*; -fn get_rng(seed: u64) -> impl rand::Rng { +fn get_rng(seed: u64) -> impl Rng { // For tests, we want a statistically good, fast, reproducible RNG. // PCG32 will do fine, and will be easy to embed if we ever need to. const INC: u64 = 11634580027462260723; @@ -80,7 +80,7 @@ fn geometric_stability() { test_samples(464, Geometric::new(0.95).unwrap(), &[0, 0, 0, 0, 1, 0, 0, 0]); // expect non-random behaviour for series of pre-determined trials - test_samples(464, Geometric::new(0.0).unwrap(), &[u64::max_value(); 100][..]); + test_samples(464, Geometric::new(0.0).unwrap(), &[u64::MAX; 100][..]); test_samples(464, Geometric::new(1.0).unwrap(), &[0; 100][..]); } diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 78bd724d789..d72620ea7c5 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -10,7 +10,7 @@ use crate::distributions::Distribution; use crate::Rng; -use core::{fmt, u64}; +use core::fmt; #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; @@ -82,7 +82,7 @@ impl fmt::Display for BernoulliError { } #[cfg(feature = "std")] -impl ::std::error::Error for BernoulliError {} +impl std::error::Error for BernoulliError {} impl Bernoulli { /// Construct a new `Bernoulli` with the given probability of success `p`. diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index 175e355ac8d..a69fa08510d 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -78,7 +78,7 @@ pub trait Distribution { DistIter { distr: self, rng, - phantom: ::core::marker::PhantomData, + phantom: core::marker::PhantomData, } } @@ -107,7 +107,7 @@ pub trait Distribution { DistMap { distr: self, func, - phantom: ::core::marker::PhantomData, + phantom: core::marker::PhantomData, } } } @@ -129,7 +129,7 @@ impl<'a, T, D: Distribution + ?Sized> Distribution for &'a D { pub struct DistIter { distr: D, rng: R, - phantom: ::core::marker::PhantomData, + phantom: core::marker::PhantomData, } impl Iterator for DistIter @@ -148,7 +148,7 @@ where } fn size_hint(&self) -> (usize, Option) { - (usize::max_value(), None) + (usize::MAX, None) } } @@ -168,7 +168,7 @@ where pub struct DistMap { distr: D, func: F, - phantom: ::core::marker::PhantomData S>, + phantom: core::marker::PhantomData S>, } impl Distribution for DistMap diff --git a/src/distributions/float.rs b/src/distributions/float.rs index da7321ee866..1eaad89d440 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -181,8 +181,8 @@ mod tests { use super::*; use crate::rngs::mock::StepRng; - const EPSILON32: f32 = ::core::f32::EPSILON; - const EPSILON64: f64 = ::core::f64::EPSILON; + const EPSILON32: f32 = f32::EPSILON; + const EPSILON64: f64 = f64::EPSILON; macro_rules! test_f32 { ($fnn:ident, $ty:ident, $ZERO:expr, $EPSILON:expr) => { diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index d1b65519353..26e1cb5decd 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -327,7 +327,7 @@ pub trait UniformSampler: Sized { impl TryFrom> for Uniform { type Error = Error; - fn try_from(r: ::core::ops::Range) -> Result, Error> { + fn try_from(r: Range) -> Result, Error> { Uniform::new(r.start, r.end) } } @@ -1526,14 +1526,14 @@ mod tests { #[test] fn test_float_overflow() { - assert_eq!(Uniform::try_from(::core::f64::MIN..::core::f64::MAX), Err(Error::NonFinite)); + assert_eq!(Uniform::try_from(f64::MIN..f64::MAX), Err(Error::NonFinite)); } #[test] #[should_panic] fn test_float_overflow_single() { let mut rng = crate::test::rng(252); - rng.gen_range(::core::f64::MIN..::core::f64::MAX); + rng.gen_range(f64::MIN..f64::MAX); } #[test] @@ -1604,7 +1604,7 @@ mod tests { (Duration::new(0, 100), Duration::new(1, 50)), ( Duration::new(0, 0), - Duration::new(u64::max_value(), 999_999_999), + Duration::new(u64::MAX, 999_999_999), ), ]; for &(low, high) in v.iter() { @@ -1706,7 +1706,7 @@ mod tests { #[test] fn value_stability() { - fn test_samples( + fn test_samples( lb: T, ub: T, expected_single: &[T], expected_multiple: &[T], ) where Uniform: Distribution { let mut rng = crate::test::rng(897); diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index dfb30ef62a4..cec292dd8a8 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -163,8 +163,8 @@ impl WeightedIndex { /// as an alternative where an update is `O(log N)`. pub fn update_weights(&mut self, new_weights: &[(usize, &X)]) -> Result<(), WeightError> where - X: for<'a> ::core::ops::AddAssign<&'a X> - + for<'a> ::core::ops::SubAssign<&'a X> + X: for<'a> core::ops::AddAssign<&'a X> + + for<'a> core::ops::SubAssign<&'a X> + Clone + Default, { @@ -278,7 +278,7 @@ where impl<'a, X> Iterator for WeightedIndexIter<'a, X> where - X: for<'b> ::core::ops::SubAssign<&'b X> + X: for<'b> core::ops::SubAssign<&'b X> + SampleUniform + PartialOrd + Clone, @@ -315,7 +315,7 @@ impl WeightedIndex { /// ``` pub fn weight(&self, index: usize) -> Option where - X: for<'a> ::core::ops::SubAssign<&'a X> + X: for<'a> core::ops::SubAssign<&'a X> { let mut weight = if index < self.cumulative_weights.len() { self.cumulative_weights[index].clone() @@ -348,7 +348,7 @@ impl WeightedIndex { /// ``` pub fn weights(&self) -> WeightedIndexIter<'_, X> where - X: for<'a> ::core::ops::SubAssign<&'a X> + X: for<'a> core::ops::SubAssign<&'a X> { WeightedIndexIter { weighted_index: self, @@ -451,22 +451,22 @@ mod test { #[test] fn test_accepting_nan() { assert_eq!( - WeightedIndex::new(&[core::f32::NAN, 0.5]).unwrap_err(), + WeightedIndex::new(&[f32::NAN, 0.5]).unwrap_err(), WeightError::InvalidWeight, ); assert_eq!( - WeightedIndex::new(&[core::f32::NAN]).unwrap_err(), + WeightedIndex::new(&[f32::NAN]).unwrap_err(), WeightError::InvalidWeight, ); assert_eq!( - WeightedIndex::new(&[0.5, core::f32::NAN]).unwrap_err(), + WeightedIndex::new(&[0.5, f32::NAN]).unwrap_err(), WeightError::InvalidWeight, ); assert_eq!( WeightedIndex::new(&[0.5, 7.0]) .unwrap() - .update_weights(&[(0, &core::f32::NAN)]) + .update_weights(&[(0, &f32::NAN)]) .unwrap_err(), WeightError::InvalidWeight, ) diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index ff3436a477b..88878cedb74 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -12,8 +12,8 @@ use core::mem::size_of_val; -use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; +use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; /// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the /// ability to reseed it. @@ -181,18 +181,16 @@ where { /// Create a new `ReseedingCore`. fn new(rng: R, threshold: u64, reseeder: Rsdr) -> Self { - use ::core::i64::MAX; - // Because generating more values than `i64::MAX` takes centuries on // current hardware, we just clamp to that value. // Also we set a threshold of 0, which indicates no limit, to that // value. let threshold = if threshold == 0 { - MAX - } else if threshold <= MAX as u64 { + i64::MAX + } else if threshold <= i64::MAX as u64 { threshold as i64 } else { - MAX + i64::MAX }; ReseedingCore { @@ -246,16 +244,16 @@ impl CryptoBlockRng for ReseedingCore where R: BlockRngCore + SeedableRng + CryptoBlockRng, Rsdr: CryptoRng, -{ -} +{} #[cfg(feature = "std_rng")] #[cfg(test)] mod test { - use super::ReseedingRng; + use crate::{Rng, SeedableRng}; use crate::rngs::mock::StepRng; use crate::rngs::std::Core; - use crate::{Rng, SeedableRng}; + + use super::ReseedingRng; #[test] fn test_reseeding() { diff --git a/src/seq/index.rs b/src/seq/index.rs index e98b7ec1061..c1f889d4f1e 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -224,7 +224,7 @@ where R: Rng + ?Sized { if amount > length { panic!("`amount` of samples must be less than or equal to `length`"); } - if length > (::core::u32::MAX as usize) { + if length > (u32::MAX as usize) { // We never want to use inplace here, but could use floyd's alg // Lazy version: always use the cache alg. return sample_rejection(rng, length, amount); @@ -282,10 +282,10 @@ where F: Fn(usize) -> X, X: Into, { - if length > (core::u32::MAX as usize) { + if length > (u32::MAX as usize) { sample_efraimidis_spirakis(rng, length, weight, amount) } else { - assert!(amount <= core::u32::MAX as usize); + assert!(amount <= u32::MAX as usize); let amount = amount as u32; let length = length as u32; sample_efraimidis_spirakis(rng, length, weight, amount) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 8d19e57df21..fc1cc993113 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -127,7 +127,7 @@ pub trait IndexedRandom: Index { Self::Output: Sized, R: Rng + ?Sized, { - let amount = ::core::cmp::min(amount, self.len()); + let amount = core::cmp::min(amount, self.len()); SliceChooseIter { slice: self, _phantom: Default::default(), @@ -173,7 +173,7 @@ pub trait IndexedRandom: Index { R: Rng + ?Sized, F: Fn(&Self::Output) -> B, B: SampleBorrow, - X: SampleUniform + Weight + ::core::cmp::PartialOrd, + X: SampleUniform + Weight + PartialOrd, { use crate::distributions::{Distribution, WeightedIndex}; let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; @@ -226,7 +226,7 @@ pub trait IndexedRandom: Index { F: Fn(&Self::Output) -> X, X: Into, { - let amount = ::core::cmp::min(amount, self.len()); + let amount = core::cmp::min(amount, self.len()); Ok(SliceChooseIter { slice: self, _phantom: Default::default(), @@ -291,7 +291,7 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { R: Rng + ?Sized, F: Fn(&Self::Output) -> B, B: SampleBorrow, - X: SampleUniform + Weight + ::core::cmp::PartialOrd, + X: SampleUniform + Weight + PartialOrd, { use crate::distributions::{Distribution, WeightedIndex}; let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; @@ -424,7 +424,7 @@ pub trait IteratorRandom: Iterator + Sized { }; } - let mut coin_flipper = coin_flipper::CoinFlipper::new(rng); + let mut coin_flipper = CoinFlipper::new(rng); let mut consumed = 0; // Continue until the iterator is exhausted @@ -669,7 +669,7 @@ impl IteratorRandom for I where I: Iterator + Sized {} #[derive(Debug)] pub struct SliceChooseIter<'a, S: ?Sized + 'a, T: 'a> { slice: &'a S, - _phantom: ::core::marker::PhantomData, + _phantom: core::marker::PhantomData, indices: index::IndexVecIntoIter, } @@ -703,7 +703,7 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator // platforms. #[inline] fn gen_index(rng: &mut R, ubound: usize) -> usize { - if ubound <= (core::u32::MAX as usize) { + if ubound <= (u32::MAX as usize) { rng.gen_range(0..ubound as u32) as usize } else { rng.gen_range(0..ubound) @@ -804,7 +804,7 @@ mod test { fn next(&mut self) -> Option { if self.chunk_remaining == 0 { - self.chunk_remaining = ::core::cmp::min(self.chunk_size, self.iter.len()); + self.chunk_remaining = core::cmp::min(self.chunk_size, self.iter.len()); } self.chunk_remaining = self.chunk_remaining.saturating_sub(1); @@ -838,7 +838,7 @@ mod test { fn size_hint(&self) -> (usize, Option) { ( - ::core::cmp::min(self.iter.len(), self.window_size), + core::cmp::min(self.iter.len(), self.window_size), if self.hint_total_size { Some(self.iter.len()) } else { @@ -1344,12 +1344,12 @@ mod test { assert_eq!(r.unwrap().count(), 0); // Case 5: NaN weights - let choices = [('a', core::f64::NAN), ('b', 1.0), ('c', 1.0)]; + let choices = [('a', f64::NAN), ('b', 1.0), ('c', 1.0)]; let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); // Case 6: +infinity weights - let choices = [('a', core::f64::INFINITY), ('b', 1.0), ('c', 1.0)]; + let choices = [('a', f64::INFINITY), ('b', 1.0), ('c', 1.0)]; for _ in 0..100 { let result = choices .choose_multiple_weighted(&mut rng, 2, |item| item.1) @@ -1360,7 +1360,7 @@ mod test { } // Case 7: -infinity weights - let choices = [('a', core::f64::NEG_INFINITY), ('b', 1.0), ('c', 1.0)]; + let choices = [('a', f64::NEG_INFINITY), ('b', 1.0), ('c', 1.0)]; let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); From eae3228c637d8cf2cd53d7c25fceee4059f56a6a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 10 Apr 2024 15:45:58 +0100 Subject: [PATCH 362/443] rand_distr/std_math: add note regarding other dependents (#1421) --- rand_distr/Cargo.toml | 5 +++++ rand_distr/README.md | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 49663743020..2d23e8a9fbf 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -23,7 +23,12 @@ rustdoc-args = ["--generate-link-to-definition"] default = ["std"] std = ["alloc", "rand/std"] alloc = ["rand/alloc"] + +# Use std's floating-point arithmetic instead of libm. +# Note that any other crate depending on `num-traits`'s `std` +# feature (default-enabled) will have the same effect. std_math = ["num-traits/std"] + serde1 = ["serde", "rand/serde1"] [dependencies] diff --git a/rand_distr/README.md b/rand_distr/README.md index f61e1b13d60..16a44bc85c9 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -25,7 +25,8 @@ The floating point functions from `num_traits` and `libm` are used to support `no_std` environments and ensure reproducibility. If the floating point functions from `std` are preferred, which may provide better accuracy and performance but may produce different random values, the `std_math` feature -can be enabled. +can be enabled. (Note that any other crate depending on `num-traits` with the +`std` feature (default-enabled) will have the same effect.) ## Crate features From d42daabf65a3ceaf58c2eefc7eb477c4d5a9b4ba Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 10 Apr 2024 16:29:51 +0100 Subject: [PATCH 363/443] Add "bug report" issue template (#1436) --- .github/ISSUE_TEMPLATE/bug_report.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..093433dffb3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,20 @@ +--- +name: Bug report +about: Something doesn't work as expected +title: '' +labels: X-bug +assignees: '' + +--- + +## Summary + +A clear and concise description of what the bug is. + +What behaviour is expected, and why? + +## Code sample + +```rust +// Code demonstrating the problem +``` From d507f7e81bab94dc5e4a55702753e34b654e8a6d Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 28 Jul 2023 20:37:42 +0200 Subject: [PATCH 364/443] Upgrade criterion --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6c7ce73f66a..539b153cc2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,7 @@ rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" -criterion = { version = "0.4" } +criterion = "0.5" [[bench]] name = "uniform" From 6f917e1f164a7e87e384931bf11969b07337dbdb Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 26 Apr 2024 14:20:51 +0200 Subject: [PATCH 365/443] Move rand benches to their own crate --- .github/workflows/test.yml | 2 +- Cargo.toml | 22 +------------ rand_benches/Cargo.toml | 32 +++++++++++++++++++ .../benches}/distributions.rs | 0 .../benches}/generators.rs | 0 {benches => rand_benches/benches}/misc.rs | 0 {benches => rand_benches/benches}/seq.rs | 0 .../benches}/seq_choose.rs | 0 {benches => rand_benches/benches}/shuffle.rs | 0 {benches => rand_benches/benches}/uniform.rs | 0 .../benches}/uniform_float.rs | 0 {benches => rand_benches/benches}/weighted.rs | 0 rand_benches/src/main.rs | 3 ++ 13 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 rand_benches/Cargo.toml rename {benches => rand_benches/benches}/distributions.rs (100%) rename {benches => rand_benches/benches}/generators.rs (100%) rename {benches => rand_benches/benches}/misc.rs (100%) rename {benches => rand_benches/benches}/seq.rs (100%) rename {benches => rand_benches/benches}/seq_choose.rs (100%) rename {benches => rand_benches/benches}/shuffle.rs (100%) rename {benches => rand_benches/benches}/uniform.rs (100%) rename {benches => rand_benches/benches}/uniform_float.rs (100%) rename {benches => rand_benches/benches}/weighted.rs (100%) create mode 100644 rand_benches/src/main.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a1c5b39b618..f37ba696870 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,7 +79,7 @@ jobs: run: | cargo test --target ${{ matrix.target }} --features=nightly cargo test --target ${{ matrix.target }} --all-features - cargo test --target ${{ matrix.target }} --benches --features=small_rng,nightly + cargo test --target ${{ matrix.target }} --manifest-path rand_benches/Cargo.toml --benches --features=small_rng,nightly cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - name: Test rand diff --git a/Cargo.toml b/Cargo.toml index 539b153cc2d..b983a1222d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ unbiased = [] [workspace] members = [ + "rand_benches", "rand_core", "rand_distr", "rand_chacha", @@ -76,24 +77,3 @@ rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" -criterion = "0.5" - -[[bench]] -name = "uniform" -path = "benches/uniform.rs" -harness = false - -[[bench]] -name = "seq_choose" -path = "benches/seq_choose.rs" -harness = false - -[[bench]] -name = "shuffle" -path = "benches/shuffle.rs" -harness = false - -[[bench]] -name = "uniform_float" -path = "benches/uniform_float.rs" -harness = false diff --git a/rand_benches/Cargo.toml b/rand_benches/Cargo.toml new file mode 100644 index 00000000000..7c7448612e0 --- /dev/null +++ b/rand_benches/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "rand_benches" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[dev-dependencies] +rand = { path = "..", features = ["small_rng", "nightly"] } +rand_pcg = { path = "../rand_pcg" } +rand_chacha = { path = "../rand_chacha" } +criterion = "0.5" + +[[bench]] +name = "uniform" +path = "benches/uniform.rs" +harness = false + +[[bench]] +name = "seq_choose" +path = "benches/seq_choose.rs" +harness = false + +[[bench]] +name = "shuffle" +path = "benches/shuffle.rs" +harness = false + +[[bench]] +name = "uniform_float" +path = "benches/uniform_float.rs" +harness = false diff --git a/benches/distributions.rs b/rand_benches/benches/distributions.rs similarity index 100% rename from benches/distributions.rs rename to rand_benches/benches/distributions.rs diff --git a/benches/generators.rs b/rand_benches/benches/generators.rs similarity index 100% rename from benches/generators.rs rename to rand_benches/benches/generators.rs diff --git a/benches/misc.rs b/rand_benches/benches/misc.rs similarity index 100% rename from benches/misc.rs rename to rand_benches/benches/misc.rs diff --git a/benches/seq.rs b/rand_benches/benches/seq.rs similarity index 100% rename from benches/seq.rs rename to rand_benches/benches/seq.rs diff --git a/benches/seq_choose.rs b/rand_benches/benches/seq_choose.rs similarity index 100% rename from benches/seq_choose.rs rename to rand_benches/benches/seq_choose.rs diff --git a/benches/shuffle.rs b/rand_benches/benches/shuffle.rs similarity index 100% rename from benches/shuffle.rs rename to rand_benches/benches/shuffle.rs diff --git a/benches/uniform.rs b/rand_benches/benches/uniform.rs similarity index 100% rename from benches/uniform.rs rename to rand_benches/benches/uniform.rs diff --git a/benches/uniform_float.rs b/rand_benches/benches/uniform_float.rs similarity index 100% rename from benches/uniform_float.rs rename to rand_benches/benches/uniform_float.rs diff --git a/benches/weighted.rs b/rand_benches/benches/weighted.rs similarity index 100% rename from benches/weighted.rs rename to rand_benches/benches/weighted.rs diff --git a/rand_benches/src/main.rs b/rand_benches/src/main.rs new file mode 100644 index 00000000000..cf98dd1b9f9 --- /dev/null +++ b/rand_benches/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Please use `cargo bench` to run the benchmarks instead."); +} From 9855f379306def0198cb1020dede60afb47322c2 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 26 Apr 2024 14:22:00 +0200 Subject: [PATCH 366/443] Pin rayon and rayon-core to support MSRV 1.61 --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b983a1222d4..0ec32ee5832 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,4 +76,5 @@ zerocopy = { version = "=0.8.0-alpha.6", default-features = false, features = [" rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } # Only to test serde1 bincode = "1.2.1" -rayon = "1.5.3" +rayon = "=1.7" +rayon-core = "=1.11" From 0935356be9e0c3b057fafa12dd258a0b10a4ea92 Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 26 Apr 2024 14:25:51 +0200 Subject: [PATCH 367/443] Fix benches test in pipeline --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f37ba696870..ee1f2032f14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,7 +79,7 @@ jobs: run: | cargo test --target ${{ matrix.target }} --features=nightly cargo test --target ${{ matrix.target }} --all-features - cargo test --target ${{ matrix.target }} --manifest-path rand_benches/Cargo.toml --benches --features=small_rng,nightly + cargo test --target ${{ matrix.target }} --manifest-path rand_benches/Cargo.toml --benches cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - name: Test rand From 13620d9775195dc11f7a60e5549970d00b0ff72e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 08:22:51 +0100 Subject: [PATCH 368/443] Test feature serde1 on rand_chacha --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee1f2032f14..3ed7c92ae3e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -104,7 +104,7 @@ jobs: - name: Test rand_pcg run: cargo test --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde1 - name: Test rand_chacha - run: cargo test --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml + run: cargo test --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml --features=serde1 test-cross: runs-on: ${{ matrix.os }} From f6ac90fc8de7ebaf35d0b5f910d8287b0112a0d6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 08:28:41 +0100 Subject: [PATCH 369/443] Updated Cargo.lock.msrv --- Cargo.lock.msrv | 118 +++++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/Cargo.lock.msrv b/Cargo.lock.msrv index 0a197d12e04..043f11775ff 100644 --- a/Cargo.lock.msrv +++ b/Cargo.lock.msrv @@ -33,15 +33,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "atty" -version = "0.2.14" +name = "anstyle" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "autocfg" @@ -51,9 +46,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "average" -version = "0.14.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d804c74bb2d66e9b7047658d21af0f1c937d7d2466410cbf1aed3b0c04048d4" +checksum = "3a237a6822e1c3c98e700b6db5b293eb341b7524dcb8d227941245702b7431dc" dependencies = [ "easy-cast", "float-ord", @@ -75,12 +70,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bumpalo" version = "3.15.4" @@ -147,25 +136,29 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.25" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ - "bitflags", - "clap_lex", - "indexmap 1.9.3", - "textwrap", + "clap_builder", ] [[package]] -name = "clap_lex" -version = "0.2.4" +name = "clap_builder" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ - "os_str_bytes", + "anstyle", + "clap_lex", ] +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -174,19 +167,19 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "criterion" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", - "atty", "cast", "ciborium", "clap", "criterion-plot", + "is-terminal", "itertools", - "lazy_static", "num-traits", + "once_cell", "oorandom", "plotters", "rayon", @@ -358,15 +351,6 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -430,6 +414,17 @@ dependencies = [ "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -454,12 +449,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.153" @@ -506,7 +495,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -522,12 +511,6 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "plotters" version = "0.3.5" @@ -591,16 +574,26 @@ name = "rand" version = "0.9.0-alpha.1" dependencies = [ "bincode", - "criterion", "log", "rand_chacha", "rand_core", "rand_pcg", "rayon", + "rayon-core", "serde", "zerocopy", ] +[[package]] +name = "rand_benches" +version = "0.1.0" +dependencies = [ + "criterion", + "rand", + "rand_chacha", + "rand_pcg", +] + [[package]] name = "rand_chacha" version = "0.9.0-alpha.1" @@ -644,9 +637,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -795,12 +788,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" - [[package]] name = "time" version = "0.3.34" @@ -968,6 +955,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.4" From 47d0b54f79edd5257d490c0d96dd5191afacee0b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 08:29:54 +0100 Subject: [PATCH 370/443] Remove pin on Rayon versions --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0ec32ee5832..9930e5cf6f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,5 +76,5 @@ zerocopy = { version = "=0.8.0-alpha.6", default-features = false, features = [" rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } # Only to test serde1 bincode = "1.2.1" -rayon = "=1.7" -rayon-core = "=1.11" +rayon = "1.7" +rayon-core = "1.11" From 3c0661b8044b507765639ed361e20c6ca16f1a7f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 08:40:53 +0100 Subject: [PATCH 371/443] Rename rand_benches -> benches --- Cargo.toml | 2 +- {rand_benches => benches}/Cargo.toml | 3 ++- {rand_benches => benches}/benches/distributions.rs | 0 {rand_benches => benches}/benches/generators.rs | 0 {rand_benches => benches}/benches/misc.rs | 0 {rand_benches => benches}/benches/seq.rs | 0 {rand_benches => benches}/benches/seq_choose.rs | 0 {rand_benches => benches}/benches/shuffle.rs | 0 {rand_benches => benches}/benches/uniform.rs | 0 {rand_benches => benches}/benches/uniform_float.rs | 0 {rand_benches => benches}/benches/weighted.rs | 0 {rand_benches => benches}/src/main.rs | 0 12 files changed, 3 insertions(+), 2 deletions(-) rename {rand_benches => benches}/Cargo.toml (94%) rename {rand_benches => benches}/benches/distributions.rs (100%) rename {rand_benches => benches}/benches/generators.rs (100%) rename {rand_benches => benches}/benches/misc.rs (100%) rename {rand_benches => benches}/benches/seq.rs (100%) rename {rand_benches => benches}/benches/seq_choose.rs (100%) rename {rand_benches => benches}/benches/shuffle.rs (100%) rename {rand_benches => benches}/benches/uniform.rs (100%) rename {rand_benches => benches}/benches/uniform_float.rs (100%) rename {rand_benches => benches}/benches/weighted.rs (100%) rename {rand_benches => benches}/src/main.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 9930e5cf6f2..10ff97c9194 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ unbiased = [] [workspace] members = [ - "rand_benches", + "benches", "rand_core", "rand_distr", "rand_chacha", diff --git a/rand_benches/Cargo.toml b/benches/Cargo.toml similarity index 94% rename from rand_benches/Cargo.toml rename to benches/Cargo.toml index 7c7448612e0..ddc141728e8 100644 --- a/rand_benches/Cargo.toml +++ b/benches/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "rand_benches" +name = "benches" version = "0.1.0" edition = "2021" +publish = false [dependencies] diff --git a/rand_benches/benches/distributions.rs b/benches/benches/distributions.rs similarity index 100% rename from rand_benches/benches/distributions.rs rename to benches/benches/distributions.rs diff --git a/rand_benches/benches/generators.rs b/benches/benches/generators.rs similarity index 100% rename from rand_benches/benches/generators.rs rename to benches/benches/generators.rs diff --git a/rand_benches/benches/misc.rs b/benches/benches/misc.rs similarity index 100% rename from rand_benches/benches/misc.rs rename to benches/benches/misc.rs diff --git a/rand_benches/benches/seq.rs b/benches/benches/seq.rs similarity index 100% rename from rand_benches/benches/seq.rs rename to benches/benches/seq.rs diff --git a/rand_benches/benches/seq_choose.rs b/benches/benches/seq_choose.rs similarity index 100% rename from rand_benches/benches/seq_choose.rs rename to benches/benches/seq_choose.rs diff --git a/rand_benches/benches/shuffle.rs b/benches/benches/shuffle.rs similarity index 100% rename from rand_benches/benches/shuffle.rs rename to benches/benches/shuffle.rs diff --git a/rand_benches/benches/uniform.rs b/benches/benches/uniform.rs similarity index 100% rename from rand_benches/benches/uniform.rs rename to benches/benches/uniform.rs diff --git a/rand_benches/benches/uniform_float.rs b/benches/benches/uniform_float.rs similarity index 100% rename from rand_benches/benches/uniform_float.rs rename to benches/benches/uniform_float.rs diff --git a/rand_benches/benches/weighted.rs b/benches/benches/weighted.rs similarity index 100% rename from rand_benches/benches/weighted.rs rename to benches/benches/weighted.rs diff --git a/rand_benches/src/main.rs b/benches/src/main.rs similarity index 100% rename from rand_benches/src/main.rs rename to benches/src/main.rs From 24b84a09a1c49f8b6e2fe8cf2a3081ba5dfa3c66 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 08:43:47 +0100 Subject: [PATCH 372/443] Move Criterion benches to benches/src --- benches/Cargo.toml | 8 ++++---- benches/src/main.rs | 3 --- benches/{benches => src}/seq_choose.rs | 0 benches/{benches => src}/shuffle.rs | 0 benches/{benches => src}/uniform.rs | 0 benches/{benches => src}/uniform_float.rs | 0 6 files changed, 4 insertions(+), 7 deletions(-) delete mode 100644 benches/src/main.rs rename benches/{benches => src}/seq_choose.rs (100%) rename benches/{benches => src}/shuffle.rs (100%) rename benches/{benches => src}/uniform.rs (100%) rename benches/{benches => src}/uniform_float.rs (100%) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index ddc141728e8..50b7640546c 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -14,20 +14,20 @@ criterion = "0.5" [[bench]] name = "uniform" -path = "benches/uniform.rs" +path = "src/uniform.rs" harness = false [[bench]] name = "seq_choose" -path = "benches/seq_choose.rs" +path = "src/seq_choose.rs" harness = false [[bench]] name = "shuffle" -path = "benches/shuffle.rs" +path = "src/shuffle.rs" harness = false [[bench]] name = "uniform_float" -path = "benches/uniform_float.rs" +path = "src/uniform_float.rs" harness = false diff --git a/benches/src/main.rs b/benches/src/main.rs deleted file mode 100644 index cf98dd1b9f9..00000000000 --- a/benches/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Please use `cargo bench` to run the benchmarks instead."); -} diff --git a/benches/benches/seq_choose.rs b/benches/src/seq_choose.rs similarity index 100% rename from benches/benches/seq_choose.rs rename to benches/src/seq_choose.rs diff --git a/benches/benches/shuffle.rs b/benches/src/shuffle.rs similarity index 100% rename from benches/benches/shuffle.rs rename to benches/src/shuffle.rs diff --git a/benches/benches/uniform.rs b/benches/src/uniform.rs similarity index 100% rename from benches/benches/uniform.rs rename to benches/src/uniform.rs diff --git a/benches/benches/uniform_float.rs b/benches/src/uniform_float.rs similarity index 100% rename from benches/benches/uniform_float.rs rename to benches/src/uniform_float.rs From e55b1f6ee5db1115ab4a105d6295b846edec6779 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 08:59:58 +0100 Subject: [PATCH 373/443] Merge rand_distr/benches into benches --- benches/Cargo.toml | 7 ++++++ ...distributions.rs => base_distributions.rs} | 0 .../benches => benches}/src/distributions.rs | 0 rand_distr/benches/Cargo.toml | 23 ------------------- 4 files changed, 7 insertions(+), 23 deletions(-) rename benches/benches/{distributions.rs => base_distributions.rs} (100%) rename {rand_distr/benches => benches}/src/distributions.rs (100%) delete mode 100644 rand_distr/benches/Cargo.toml diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 50b7640546c..b3068c2f758 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -10,7 +10,14 @@ publish = false rand = { path = "..", features = ["small_rng", "nightly"] } rand_pcg = { path = "../rand_pcg" } rand_chacha = { path = "../rand_chacha" } +rand_distr = { path = "../rand_distr" } criterion = "0.5" +criterion-cycles-per-byte = "0.6" + +[[bench]] +name = "distributions" +path = "src/distributions.rs" +harness = false [[bench]] name = "uniform" diff --git a/benches/benches/distributions.rs b/benches/benches/base_distributions.rs similarity index 100% rename from benches/benches/distributions.rs rename to benches/benches/base_distributions.rs diff --git a/rand_distr/benches/src/distributions.rs b/benches/src/distributions.rs similarity index 100% rename from rand_distr/benches/src/distributions.rs rename to benches/src/distributions.rs diff --git a/rand_distr/benches/Cargo.toml b/rand_distr/benches/Cargo.toml deleted file mode 100644 index e9c41c31540..00000000000 --- a/rand_distr/benches/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "benches" -version = "0.0.0" -authors = ["The Rand Project Developers"] -license = "MIT OR Apache-2.0" -description = "Criterion benchmarks of the rand_distr crate" -edition = "2021" -rust-version = "1.61" -publish = false - -[workspace] - -[dependencies] -criterion = { version = "0.3", features = ["html_reports"] } -criterion-cycles-per-byte = "0.1" -rand = { path = "../../" } -rand_distr = { path = "../" } -rand_pcg = { path = "../../rand_pcg/" } - -[[bench]] -name = "distributions" -path = "src/distributions.rs" -harness = false From 75a233b02a5941dbf46fdb8fe3895336263f61a8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 09:06:46 +0100 Subject: [PATCH 374/443] Add note to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9019ae4b8b5..ba7fd2d7a0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [Unreleased] - Add `rand::distributions::WeightedIndex::{weight, weights, total_weight}` (#1420) - Bump the MSRV to 1.61.0 +- Move all benchmarks to new `benches` crate (#1439) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) From 6389320091bc71328e2873a4658a92b8745d41b3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 09:17:29 +0100 Subject: [PATCH 375/443] Update Cargo.lock.msrv again --- Cargo.lock.msrv | 32 +++++++++++++++++++++----------- Cargo.toml | 1 - 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Cargo.lock.msrv b/Cargo.lock.msrv index 043f11775ff..bf15ecb24d7 100644 --- a/Cargo.lock.msrv +++ b/Cargo.lock.msrv @@ -61,6 +61,18 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "benches" +version = "0.1.0" +dependencies = [ + "criterion", + "criterion-cycles-per-byte", + "rand", + "rand_chacha", + "rand_distr", + "rand_pcg", +] + [[package]] name = "bincode" version = "1.3.3" @@ -191,6 +203,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "criterion-cycles-per-byte" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5281161544b8f2397e14942c2045efa3446470348121a65c37263f8e76c1e2ff" +dependencies = [ + "criterion", +] + [[package]] name = "criterion-plot" version = "0.5.0" @@ -579,21 +600,10 @@ dependencies = [ "rand_core", "rand_pcg", "rayon", - "rayon-core", "serde", "zerocopy", ] -[[package]] -name = "rand_benches" -version = "0.1.0" -dependencies = [ - "criterion", - "rand", - "rand_chacha", - "rand_pcg", -] - [[package]] name = "rand_chacha" version = "0.9.0-alpha.1" diff --git a/Cargo.toml b/Cargo.toml index 10ff97c9194..48f8719e292 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,4 +77,3 @@ rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } # Only to test serde1 bincode = "1.2.1" rayon = "1.7" -rayon-core = "1.11" From bf0301bfe6d2360e6c86a6c58273f7069f027691 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 27 Apr 2024 09:25:00 +0100 Subject: [PATCH 376/443] Fix test.yml --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ed7c92ae3e..495571d2620 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -79,8 +79,7 @@ jobs: run: | cargo test --target ${{ matrix.target }} --features=nightly cargo test --target ${{ matrix.target }} --all-features - cargo test --target ${{ matrix.target }} --manifest-path rand_benches/Cargo.toml --benches - cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches + cargo test --target ${{ matrix.target }} --manifest-path benches/Cargo.toml --benches cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - name: Test rand run: | From 4f8257a98685e19711d9e34708df594747ade98b Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Mon, 29 Apr 2024 20:41:10 +0200 Subject: [PATCH 377/443] Rename `Rng::gen` to `Rng::random` (#1438) --- CHANGELOG.md | 1 + benches/benches/misc.rs | 8 ++++---- examples/monty-hall.rs | 2 +- src/distributions/bernoulli.rs | 2 +- src/distributions/float.rs | 18 +++++++++--------- src/distributions/integer.rs | 4 ++-- src/distributions/mod.rs | 8 ++++---- src/distributions/other.rs | 20 ++++++++++---------- src/distributions/uniform.rs | 24 ++++++++++++------------ src/lib.rs | 6 +++--- src/prelude.rs | 2 +- src/rng.rs | 32 ++++++++++++++++++++------------ src/rngs/mock.rs | 2 +- src/rngs/reseeding.rs | 10 +++++----- src/rngs/thread.rs | 2 +- src/seq/index.rs | 2 +- 16 files changed, 76 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba7fd2d7a0e..96d9c3eb9a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [Unreleased] - Add `rand::distributions::WeightedIndex::{weight, weights, total_weight}` (#1420) - Bump the MSRV to 1.61.0 +- Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) - Move all benchmarks to new `benches` crate (#1439) ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/benches/benches/misc.rs b/benches/benches/misc.rs index a3aef17c57e..a3c353bbdd6 100644 --- a/benches/benches/misc.rs +++ b/benches/benches/misc.rs @@ -101,7 +101,7 @@ fn gen_1kb_u16_iter_repeat(b: &mut Bencher) { use core::iter; let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { - let v: Vec = iter::repeat(()).map(|()| rng.gen()).take(512).collect(); + let v: Vec = iter::repeat(()).map(|()| rng.random()).take(512).collect(); v }); b.bytes = 1024; @@ -122,7 +122,7 @@ fn gen_1kb_u16_gen_array(b: &mut Bencher) { let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { // max supported array length is 32! - let v: [[u16; 32]; 16] = rng.gen(); + let v: [[u16; 32]; 16] = rng.random(); v }); b.bytes = 1024; @@ -144,7 +144,7 @@ fn gen_1kb_u64_iter_repeat(b: &mut Bencher) { use core::iter; let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { - let v: Vec = iter::repeat(()).map(|()| rng.gen()).take(128).collect(); + let v: Vec = iter::repeat(()).map(|()| rng.random()).take(128).collect(); v }); b.bytes = 1024; @@ -165,7 +165,7 @@ fn gen_1kb_u64_gen_array(b: &mut Bencher) { let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); b.iter(|| { // max supported array length is 32! - let v: [[u64; 32]; 4] = rng.gen(); + let v: [[u64; 32]; 4] = rng.random(); v }); b.bytes = 1024; diff --git a/examples/monty-hall.rs b/examples/monty-hall.rs index bb4bd870007..aaff41cad33 100644 --- a/examples/monty-hall.rs +++ b/examples/monty-hall.rs @@ -45,7 +45,7 @@ fn simulate(random_door: &Uniform, rng: &mut R) -> SimulationResult let open = game_host_open(car, choice, rng); // Shall we switch? - let switch = rng.gen(); + let switch = rng.random(); if switch { choice = switch_door(choice, open); } diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index d72620ea7c5..a68a82965ce 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -136,7 +136,7 @@ impl Distribution for Bernoulli { if self.p_int == ALWAYS_TRUE { return true; } - let v: u64 = rng.gen(); + let v: u64 = rng.random(); v < self.p_int } } diff --git a/src/distributions/float.rs b/src/distributions/float.rs index 1eaad89d440..ace6fe66119 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -115,7 +115,7 @@ macro_rules! float_impls { let precision = $fraction_bits + 1; let scale = 1.0 / ((1 as $u_scalar << precision) as $f_scalar); - let value: $uty = rng.gen(); + let value: $uty = rng.random(); let value = value >> $uty::splat(float_size - precision); $ty::splat(scale) * $ty::cast_from_int(value) } @@ -132,7 +132,7 @@ macro_rules! float_impls { let precision = $fraction_bits + 1; let scale = 1.0 / ((1 as $u_scalar << precision) as $f_scalar); - let value: $uty = rng.gen(); + let value: $uty = rng.random(); let value = value >> $uty::splat(float_size - precision); // Add 1 to shift up; will not overflow because of right-shift: $ty::splat(scale) * $ty::cast_from_int(value + $uty::splat(1)) @@ -149,7 +149,7 @@ macro_rules! float_impls { use core::$f_scalar::EPSILON; let float_size = mem::size_of::<$f_scalar>() as $u_scalar * 8; - let value: $uty = rng.gen(); + let value: $uty = rng.random(); let fraction = value >> $uty::splat(float_size - $fraction_bits); fraction.into_float_with_exponent(0) - $ty::splat(1.0 - EPSILON / 2.0) } @@ -192,11 +192,11 @@ mod tests { // Standard let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.gen::<$ty>(), $ZERO); + assert_eq!(zeros.random::<$ty>(), $ZERO); let mut one = StepRng::new(1 << 8 | 1 << (8 + 32), 0); - assert_eq!(one.gen::<$ty>(), $EPSILON / two); + assert_eq!(one.random::<$ty>(), $EPSILON / two); let mut max = StepRng::new(!0, 0); - assert_eq!(max.gen::<$ty>(), $ty::splat(1.0) - $EPSILON / two); + assert_eq!(max.random::<$ty>(), $ty::splat(1.0) - $EPSILON / two); // OpenClosed01 let mut zeros = StepRng::new(0, 0); @@ -234,11 +234,11 @@ mod tests { // Standard let mut zeros = StepRng::new(0, 0); - assert_eq!(zeros.gen::<$ty>(), $ZERO); + assert_eq!(zeros.random::<$ty>(), $ZERO); let mut one = StepRng::new(1 << 11, 0); - assert_eq!(one.gen::<$ty>(), $EPSILON / two); + assert_eq!(one.random::<$ty>(), $EPSILON / two); let mut max = StepRng::new(!0, 0); - assert_eq!(max.gen::<$ty>(), $ty::splat(1.0) - $EPSILON / two); + assert_eq!(max.random::<$ty>(), $ty::splat(1.0) - $EPSILON / two); // OpenClosed01 let mut zeros = StepRng::new(0, 0); diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index d7bb988d1be..ca27c6331d8 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -81,7 +81,7 @@ macro_rules! impl_int_from_uint { impl Distribution<$ty> for Standard { #[inline] fn sample(&self, rng: &mut R) -> $ty { - rng.gen::<$uty>() as $ty + rng.random::<$uty>() as $ty } } }; @@ -99,7 +99,7 @@ macro_rules! impl_nzint { impl Distribution<$ty> for Standard { fn sample(&self, rng: &mut R) -> $ty { loop { - if let Some(nz) = $new(rng.gen()) { + if let Some(nz) = $new(rng.random()) { break nz; } } diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index ee3615cf922..050e75d98ea 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -11,7 +11,7 @@ //! //! This module is the home of the [`Distribution`] trait and several of its //! implementations. It is the workhorse behind some of the convenient -//! functionality of the [`Rng`] trait, e.g. [`Rng::gen`] and of course +//! functionality of the [`Rng`] trait, e.g. [`Rng::random`] and of course //! [`Rng::sample`]. //! //! Abstractly, a [probability distribution] describes the probability of @@ -31,13 +31,13 @@ //! # The `Standard` distribution //! //! The [`Standard`] distribution is important to mention. This is the -//! distribution used by [`Rng::gen`] and represents the "default" way to +//! distribution used by [`Rng::random`] and represents the "default" way to //! produce a random value for many different types, including most primitive //! types, tuples, arrays, and a few derived types. See the documentation of //! [`Standard`] for more details. //! //! Implementing `Distribution` for [`Standard`] for user types `T` makes it -//! possible to generate type `T` with [`Rng::gen`], and by extension also +//! possible to generate type `T` with [`Rng::random`], and by extension also //! with the [`random`] function. //! //! ## Random characters @@ -181,7 +181,7 @@ use crate::Rng; /// /// impl Distribution for Standard { /// fn sample(&self, rng: &mut R) -> MyF32 { -/// MyF32 { x: rng.gen() } +/// MyF32 { x: rng.random() } /// } /// } /// ``` diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 5b922bd37e2..7cb63cc8365 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -156,10 +156,10 @@ impl Distribution for Standard { /// /// ```ignore /// // this may be faster... -/// let x = unsafe { _mm_blendv_epi8(a.into(), b.into(), rng.gen::<__m128i>()) }; +/// let x = unsafe { _mm_blendv_epi8(a.into(), b.into(), rng.random::<__m128i>()) }; /// /// // ...than this -/// let x = rng.gen::().select(b, a); +/// let x = rng.random::().select(b, a); /// ``` /// /// Since most bits are unused you could also generate only as many bits as you need, i.e.: @@ -169,7 +169,7 @@ impl Distribution for Standard { /// use rand::prelude::*; /// let mut rng = thread_rng(); /// -/// let x = u16x8::splat(rng.gen::() as u16); +/// let x = u16x8::splat(rng.random::() as u16); /// let mask = u16x8::splat(1) << u16x8::from([0, 1, 2, 3, 4, 5, 6, 7]); /// let rand_mask = (x & mask).simd_eq(mask); /// ``` @@ -189,7 +189,7 @@ where fn sample(&self, rng: &mut R) -> Mask { // `MaskElement` must be a signed integer, so this is equivalent // to the scalar `i32 < 0` method - let var = rng.gen::>(); + let var = rng.random::>(); var.simd_lt(Simd::default()) } } @@ -208,7 +208,7 @@ macro_rules! tuple_impl { let out = ($( // use the $tyvar's to get the appropriate number of // repeats (they're not actually needed) - rng.gen::<$tyvar>() + rng.random::<$tyvar>() ,)*); // Suppress the unused variable warning for empty tuple @@ -247,7 +247,7 @@ where Standard: Distribution let mut buff: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; for elem in &mut buff { - *elem = MaybeUninit::new(_rng.gen()); + *elem = MaybeUninit::new(_rng.random()); } unsafe { mem::transmute_copy::<_, _>(&buff) } @@ -260,8 +260,8 @@ where Standard: Distribution #[inline] fn sample(&self, rng: &mut R) -> Option { // UFCS is needed here: https://github.com/rust-lang/rust/issues/24066 - if rng.gen::() { - Some(rng.gen()) + if rng.random::() { + Some(rng.random()) } else { None } @@ -273,7 +273,7 @@ where Standard: Distribution { #[inline] fn sample(&self, rng: &mut R) -> Wrapping { - Wrapping(rng.gen()) + Wrapping(rng.random()) } } @@ -300,7 +300,7 @@ mod tests { // Test by generating a relatively large number of chars, so we also // take the rejection sampling path. let word: String = iter::repeat(()) - .map(|()| rng.gen::()) + .map(|()| rng.random::()) .take(1000) .collect(); assert!(!word.is_empty()); diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 26e1cb5decd..7fbf0fced25 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -515,12 +515,12 @@ macro_rules! uniform_int_impl { fn sample(&self, rng: &mut R) -> Self::X { let range = self.range as $uty as $sample_ty; if range == 0 { - return rng.gen(); + return rng.random(); } let thresh = self.thresh as $uty as $sample_ty; let hi = loop { - let (hi, lo) = rng.gen::<$sample_ty>().wmul(range); + let (hi, lo) = rng.random::<$sample_ty>().wmul(range); if lo >= thresh { break hi; } @@ -563,16 +563,16 @@ macro_rules! uniform_int_impl { let range = high.wrapping_sub(low).wrapping_add(1) as $uty as $sample_ty; if range == 0 { // Range is MAX+1 (unrepresentable), so we need a special case - return Ok(rng.gen()); + return Ok(rng.random()); } // generate a sample using a sensible integer type - let (mut result, lo_order) = rng.gen::<$sample_ty>().wmul(range); + let (mut result, lo_order) = rng.random::<$sample_ty>().wmul(range); // if the sample is biased... if lo_order > range.wrapping_neg() { // ...generate a new sample to reduce bias... - let (new_hi_order, _) = (rng.gen::<$sample_ty>()).wmul(range as $sample_ty); + let (new_hi_order, _) = (rng.random::<$sample_ty>()).wmul(range as $sample_ty); // ... incrementing result on overflow let is_overflow = lo_order.checked_add(new_hi_order as $sample_ty).is_none(); result += is_overflow as $sample_ty; @@ -602,11 +602,11 @@ macro_rules! uniform_int_impl { return Ok(rng.gen()); } - let (mut result, mut lo) = rng.gen::<$sample_ty>().wmul(range); + let (mut result, mut lo) = rng.random::<$sample_ty>().wmul(range); // In contrast to the biased sampler, we use a loop: while lo > range.wrapping_neg() { - let (new_hi, new_lo) = (rng.gen::<$sample_ty>()).wmul(range); + let (new_hi, new_lo) = (rng.random::<$sample_ty>()).wmul(range); match lo.checked_add(new_hi) { Some(x) if x < $sample_ty::MAX => { // Anything less than MAX: last term is 0 @@ -732,7 +732,7 @@ macro_rules! uniform_simd_int_impl { // rejection. The replacement method does however add a little // overhead. Benchmarking or calculating probabilities might // reveal contexts where this replacement method is slower. - let mut v: Simd<$unsigned, LANES> = rng.gen(); + let mut v: Simd<$unsigned, LANES> = rng.random(); loop { let (hi, lo) = v.wmul(range); let mask = lo.simd_ge(thresh); @@ -747,7 +747,7 @@ macro_rules! uniform_simd_int_impl { return range.simd_gt(Simd::splat(0)).select(result, v); } // Replace only the failing lanes - v = mask.select(v, rng.gen()); + v = mask.select(v, rng.random()); } } } @@ -970,7 +970,7 @@ macro_rules! uniform_float_impl { fn sample(&self, rng: &mut R) -> Self::X { // Generate a value in the range [1, 2) - let value1_2 = (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); + let value1_2 = (rng.random::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); // Get a value in the range [0, 1) to avoid overflow when multiplying by scale let value0_1 = value1_2 - <$ty>::splat(1.0); @@ -1006,7 +1006,7 @@ macro_rules! uniform_float_impl { loop { // Generate a value in the range [1, 2) let value1_2 = - (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); + (rng.random::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); // Get a value in the range [0, 1) to avoid overflow when multiplying by scale let value0_1 = value1_2 - <$ty>::splat(1.0); @@ -1079,7 +1079,7 @@ macro_rules! uniform_float_impl { // Generate a value in the range [1, 2) let value1_2 = - (rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); + (rng.random::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); // Get a value in the range [0, 1) to avoid overflow when multiplying by scale let value0_1 = value1_2 - <$ty>::splat(1.0); diff --git a/src/lib.rs b/src/lib.rs index dc9e29d6277..5410b16f33b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ //! } //! //! let mut rng = rand::thread_rng(); -//! let y: f64 = rng.gen(); // generates a float between 0 and 1 +//! let y: f64 = rng.random(); // generates a float between 0 and 1 //! //! let mut nums: Vec = (1..100).collect(); //! nums.shuffle(&mut rng); @@ -147,7 +147,7 @@ use crate::distributions::{Distribution, Standard}; /// let mut rng = rand::thread_rng(); /// /// for x in v.iter_mut() { -/// *x = rng.gen(); +/// *x = rng.random(); /// } /// ``` /// @@ -158,7 +158,7 @@ use crate::distributions::{Distribution, Standard}; #[inline] pub fn random() -> T where Standard: Distribution { - thread_rng().gen() + thread_rng().random() } #[cfg(test)] diff --git a/src/prelude.rs b/src/prelude.rs index 35fee3d73fd..2f9fa4c8ff3 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,7 +15,7 @@ //! ``` //! use rand::prelude::*; //! # let mut r = StdRng::from_rng(thread_rng()).unwrap(); -//! # let _: f32 = r.gen(); +//! # let _: f32 = r.random(); //! ``` #[doc(no_inline)] pub use crate::distributions::Distribution; diff --git a/src/rng.rs b/src/rng.rs index 206275f8d76..d9927197cc3 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -37,7 +37,7 @@ use core::{mem, slice}; /// on references (including type-erased references). Unfortunately within the /// function `foo` it is not known whether `rng` is a reference type or not, /// hence many uses of `rng` require an extra reference, either explicitly -/// (`distr.sample(&mut rng)`) or implicitly (`rng.gen()`); one may hope the +/// (`distr.sample(&mut rng)`) or implicitly (`rng.random()`); one may hope the /// optimiser can remove redundant references later. /// /// Example: @@ -47,7 +47,7 @@ use core::{mem, slice}; /// use rand::Rng; /// /// fn foo(rng: &mut R) -> f32 { -/// rng.gen() +/// rng.random() /// } /// /// # let v = foo(&mut thread_rng()); @@ -61,14 +61,14 @@ pub trait Rng: RngCore { /// use rand::{thread_rng, Rng}; /// /// let mut rng = thread_rng(); - /// let x: u32 = rng.gen(); + /// let x: u32 = rng.random(); /// println!("{}", x); - /// println!("{:?}", rng.gen::<(f64, bool)>()); + /// println!("{:?}", rng.random::<(f64, bool)>()); /// ``` /// /// # Arrays and tuples /// - /// The `rng.gen()` method is able to generate arrays + /// The `rng.random()` method is able to generate arrays /// and tuples (up to 12 elements), so long as all element types can be /// generated. /// @@ -79,16 +79,16 @@ pub trait Rng: RngCore { /// use rand::{thread_rng, Rng}; /// /// let mut rng = thread_rng(); - /// let tuple: (u8, i32, char) = rng.gen(); // arbitrary tuple support + /// let tuple: (u8, i32, char) = rng.random(); // arbitrary tuple support /// - /// let arr1: [f32; 32] = rng.gen(); // array construction + /// let arr1: [f32; 32] = rng.random(); // array construction /// let mut arr2 = [0u8; 128]; /// rng.fill(&mut arr2); // array fill /// ``` /// /// [`Standard`]: distributions::Standard #[inline] - fn gen(&mut self) -> T + fn random(&mut self) -> T where Standard: Distribution { Standard.sample(self) } @@ -321,6 +321,14 @@ pub trait Rng: RngCore { let d = distributions::Bernoulli::from_ratio(numerator, denominator).unwrap(); self.sample(d) } + + /// Alias for [`Rng::random`]. + #[inline] + #[deprecated(since="0.9.0", note="Renamed to `random` to avoid conflict with the new `gen` keyword in Rust 2024.")] + fn gen(&mut self) -> T + where Standard: Distribution { + self.random() + } } impl Rng for R {} @@ -343,7 +351,7 @@ macro_rules! impl_fill_each { impl Fill for [$t] { fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { for elt in self.iter_mut() { - *elt = rng.gen(); + *elt = rng.random(); } Ok(()) } @@ -474,7 +482,7 @@ mod test { // Check equivalence for generated floats let mut array = [0f32; 2]; rng.fill(&mut array); - let gen: [f32; 2] = rng.gen(); + let gen: [f32; 2] = rng.random(); assert_eq!(array, gen); } @@ -555,7 +563,7 @@ mod test { let mut rng = rng(109); let mut r = &mut rng as &mut dyn RngCore; r.next_u32(); - r.gen::(); + r.random::(); assert_eq!(r.gen_range(0..1), 0); let _c: u8 = Standard.sample(&mut r); } @@ -567,7 +575,7 @@ mod test { let rng = rng(110); let mut r = Box::new(rng) as Box; r.next_u32(); - r.gen::(); + r.random::(); assert_eq!(r.gen_range(0..1), 0); let _c: u8 = Standard.sample(&mut r); } diff --git a/src/rngs/mock.rs b/src/rngs/mock.rs index 0d9e0f905c9..66705a008cd 100644 --- a/src/rngs/mock.rs +++ b/src/rngs/mock.rs @@ -35,7 +35,7 @@ use serde::{Serialize, Deserialize}; /// use rand::rngs::mock::StepRng; /// /// let mut my_rng = StepRng::new(2, 1); -/// let sample: [u64; 3] = my_rng.gen(); +/// let sample: [u64; 3] = my_rng.random(); /// assert_eq!(sample, [2, 3, 4]); /// ``` #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index 88878cedb74..752952e67ea 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -62,10 +62,10 @@ use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; /// let prng = ChaCha20Core::from_entropy(); /// let mut reseeding_rng = ReseedingRng::new(prng, 0, OsRng); /// -/// println!("{}", reseeding_rng.gen::()); +/// println!("{}", reseeding_rng.random::()); /// /// let mut cloned_rng = reseeding_rng.clone(); -/// assert!(reseeding_rng.gen::() != cloned_rng.gen::()); +/// assert!(reseeding_rng.random::() != cloned_rng.random::()); /// ``` /// /// [`BlockRngCore`]: rand_core::block::BlockRngCore @@ -283,12 +283,12 @@ mod test { let rng = Core::from_rng(&mut zero).unwrap(); let mut rng1 = ReseedingRng::new(rng, 32 * 4, zero); - let first: u32 = rng1.gen(); + let first: u32 = rng1.random(); for _ in 0..10 { - let _ = rng1.gen::(); + let _ = rng1.random::(); } let mut rng2 = rng1.clone(); - assert_eq!(first, rng2.gen::()); + assert_eq!(first, rng2.random::()); } } diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 449d6d6cf48..f79ded83e35 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -185,7 +185,7 @@ mod test { fn test_thread_rng() { use crate::Rng; let mut r = crate::thread_rng(); - r.gen::(); + r.random::(); assert_eq!(r.gen_range(0..1), 0); } diff --git a/src/seq/index.rs b/src/seq/index.rs index c1f889d4f1e..e7b1e2b22f9 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -348,7 +348,7 @@ where while index < length { let weight = weight(index.as_usize()).into(); if weight > 0.0 { - let key = rng.gen::().powf(1.0 / weight); + let key = rng.random::().powf(1.0 / weight); candidates.push(Element { index, key }); } else if !(weight >= 0.0) { return Err(WeightError::InvalidWeight); From a2375dceaf85446d294721476d2174dc967a81a4 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sun, 5 May 2024 00:19:29 -0700 Subject: [PATCH 378/443] Add #[track_caller] to methods which panic (#1442) * Add #[track_caller] to methods which panic This makes it easier to diagnose problems when the preconidtions of these methods are not met. Signed-off-by: Joe Richey --- CHANGELOG.md | 1 + rand_core/src/lib.rs | 1 + src/rng.rs | 27 +++++++++++++++++++-------- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96d9c3eb9a4..44994339514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Bump the MSRV to 1.61.0 - Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) - Move all benchmarks to new `benches` crate (#1439) +- Annotate panicking methods with `#[track_caller]` (#1442) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 70424e6c9c0..6617ea1c803 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -385,6 +385,7 @@ pub trait SeedableRng: Sized { /// [`getrandom`]: https://docs.rs/getrandom #[cfg(feature = "getrandom")] #[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] + #[track_caller] fn from_entropy() -> Self { let mut seed = Self::Seed::default(); if let Err(err) = getrandom::getrandom(seed.as_mut()) { diff --git a/src/rng.rs b/src/rng.rs index d9927197cc3..6c9d4b7eabf 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -9,11 +9,11 @@ //! [`Rng`] trait -use rand_core::{Error, RngCore}; use crate::distributions::uniform::{SampleRange, SampleUniform}; use crate::distributions::{self, Distribution, Standard}; use core::num::Wrapping; use core::{mem, slice}; +use rand_core::{Error, RngCore}; /// An automatically-implemented extension trait on [`RngCore`] providing high-level /// generic methods for sampling values and other convenience methods. @@ -124,10 +124,11 @@ pub trait Rng: RngCore { /// ``` /// /// [`Uniform`]: distributions::uniform::Uniform + #[track_caller] fn gen_range(&mut self, range: R) -> T where T: SampleUniform, - R: SampleRange + R: SampleRange, { assert!(!range.is_empty(), "cannot sample empty range"); range.sample_single(self).unwrap() @@ -236,8 +237,9 @@ pub trait Rng: RngCore { /// /// [`fill_bytes`]: RngCore::fill_bytes /// [`try_fill`]: Rng::try_fill + #[track_caller] fn fill(&mut self, dest: &mut T) { - dest.try_fill(self).unwrap_or_else(|_| panic!("Rng::fill failed")) + dest.try_fill(self).expect("Rng::fill failed") } /// Fill any type implementing [`Fill`] with random data @@ -288,9 +290,12 @@ pub trait Rng: RngCore { /// /// [`Bernoulli`]: distributions::Bernoulli #[inline] + #[track_caller] fn gen_bool(&mut self, p: f64) -> bool { - let d = distributions::Bernoulli::new(p).unwrap(); - self.sample(d) + match distributions::Bernoulli::new(p) { + Ok(d) => self.sample(d), + Err(_) => panic!("p={:?} is outside range [0.0, 1.0]", p), + } } /// Return a bool with a probability of `numerator/denominator` of being @@ -317,9 +322,15 @@ pub trait Rng: RngCore { /// /// [`Bernoulli`]: distributions::Bernoulli #[inline] + #[track_caller] fn gen_ratio(&mut self, numerator: u32, denominator: u32) -> bool { - let d = distributions::Bernoulli::from_ratio(numerator, denominator).unwrap(); - self.sample(d) + match distributions::Bernoulli::from_ratio(numerator, denominator) { + Ok(d) => self.sample(d), + Err(_) => panic!( + "p={}/{} is outside range [0.0, 1.0]", + numerator, denominator + ), + } } /// Alias for [`Rng::random`]. @@ -432,8 +443,8 @@ where [T]: Fill #[cfg(test)] mod test { use super::*; - use crate::test::rng; use crate::rngs::mock::StepRng; + use crate::test::rng; #[cfg(feature = "alloc")] use alloc::boxed::Box; #[test] From b10172f2cf8d0ace6512bde1a9151c944a4a6344 Mon Sep 17 00:00:00 2001 From: Makro <4398091+xmakro@users.noreply.github.com> Date: Tue, 7 May 2024 11:25:40 +0100 Subject: [PATCH 379/443] Make fn WeightedTreeIndex::try_sample public --- rand_distr/src/weighted_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index d5b4ef467d8..e8a3681f3c9 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -247,7 +247,7 @@ impl + Weight> /// /// Returns an error if there are no elements or all weights are zero. This /// is unlike [`Distribution::sample`], which panics in those cases. - fn try_sample(&self, rng: &mut R) -> Result { + pub fn try_sample(&self, rng: &mut R) -> Result { let total_weight = self.subtotals.first().cloned().unwrap_or(W::ZERO); if total_weight == W::ZERO { return Err(WeightError::InsufficientNonZero); From 1f818787ca1f3d00a9044003e974736072683963 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Tue, 7 May 2024 04:38:30 -0700 Subject: [PATCH 380/443] Use zerocopy 0.7.33, not 0.8.0-alpha (#1446) --- Cargo.toml | 2 +- rand_core/Cargo.toml | 2 +- rand_core/src/impls.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48f8719e292..8ffdff78190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ rand_core = { path = "rand_core", version = "=0.9.0-alpha.1", default-features = log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.1", default-features = false, optional = true } -zerocopy = { version = "=0.8.0-alpha.6", default-features = false, features = ["simd"] } +zerocopy = { version = "0.7.33", default-features = false, features = ["simd"] } [dev-dependencies] rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 642e9a12ad6..7c4118f632d 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -32,4 +32,4 @@ serde1 = ["serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } getrandom = { version = "0.2", optional = true } -zerocopy = { version = "=0.8.0-alpha.6", default-features = false } +zerocopy = { version = "0.7.33", default-features = false } diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index a8fc1a7e8c6..d7dcd3457d3 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -19,7 +19,7 @@ use crate::RngCore; use core::cmp::min; -use zerocopy::{IntoBytes, NoCell}; +use zerocopy::AsBytes; /// Implement `next_u64` via `next_u32`, little-endian order. pub fn next_u64_via_u32(rng: &mut R) -> u64 { @@ -53,7 +53,7 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } } -trait Observable: IntoBytes + NoCell + Copy { +trait Observable: AsBytes + Copy { fn to_le(self) -> Self; } impl Observable for u32 { From fba5521f0f0f085e6e414562b762b5bf514ed9df Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 8 May 2024 16:10:32 +0300 Subject: [PATCH 381/443] Add `TryRngCore` and `TryCryptoRng` traits (#1424) This reworks fallibility, replacing the fixed `Error` type. --- README.md | 2 +- benches/benches/base_distributions.rs | 22 +-- benches/benches/generators.rs | 65 +++---- benches/benches/misc.rs | 28 +-- benches/benches/seq.rs | 14 +- benches/src/distributions.rs | 21 +-- benches/src/uniform.rs | 8 +- benches/src/uniform_float.rs | 12 +- rand_chacha/README.md | 2 +- rand_chacha/src/chacha.rs | 91 +++++----- rand_core/src/blanket_impls.rs | 91 ++++++++++ rand_core/src/block.rs | 73 ++++---- rand_core/src/error.rs | 226 ------------------------ rand_core/src/impls.rs | 52 +++++- rand_core/src/lib.rs | 244 ++++++++++++++------------ rand_core/src/os.rs | 44 ++--- rand_distr/tests/uniformity.rs | 4 +- rand_pcg/src/lib.rs | 2 +- rand_pcg/src/pcg128.rs | 14 +- rand_pcg/src/pcg128cm.rs | 12 +- rand_pcg/src/pcg64.rs | 8 +- rand_pcg/tests/lcg128cmdxsm64.rs | 2 +- rand_pcg/tests/lcg128xsl64.rs | 2 +- rand_pcg/tests/lcg64xsh32.rs | 2 +- rand_pcg/tests/mcg128xsl64.rs | 2 +- src/distributions/distribution.rs | 9 +- src/distributions/mod.rs | 2 +- src/lib.rs | 9 +- src/prelude.rs | 5 +- src/rng.rs | 86 ++++----- src/rngs/mock.rs | 30 ++-- src/rngs/mod.rs | 17 +- src/rngs/reseeding.rs | 45 +++-- src/rngs/small.rs | 15 +- src/rngs/std.rs | 21 +-- src/rngs/thread.rs | 35 ++-- src/rngs/xoshiro128plusplus.rs | 35 ++-- src/rngs/xoshiro256plusplus.rs | 48 ++--- 38 files changed, 641 insertions(+), 759 deletions(-) create mode 100644 rand_core/src/blanket_impls.rs delete mode 100644 rand_core/src/error.rs diff --git a/README.md b/README.md index ab080dfc050..9fa7a2f8528 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ compiler versions will be compatible. This is especially true of Rand's experimental `simd_support` feature. Rand supports limited functionality in `no_std` mode (enabled via -`default-features = false`). In this case, `OsRng` and `from_entropy` are +`default-features = false`). In this case, `OsRng` and `from_os_rng` are unavailable (unless `getrandom` is enabled), large parts of `seq` are unavailable (unless `alloc` is enabled), and `thread_rng` and `random` are unavailable. diff --git a/benches/benches/base_distributions.rs b/benches/benches/base_distributions.rs index 6b6bd39735a..c60ce47aeab 100644 --- a/benches/benches/base_distributions.rs +++ b/benches/benches/base_distributions.rs @@ -32,7 +32,7 @@ macro_rules! distr_int { ($fnn:ident, $ty:ty, $distr:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; b.iter(|| { @@ -52,7 +52,7 @@ macro_rules! distr_nz_int { ($fnn:ident, $tynz:ty, $ty:ty, $distr:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; b.iter(|| { @@ -72,7 +72,7 @@ macro_rules! distr_float { ($fnn:ident, $ty:ty, $distr:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; b.iter(|| { @@ -92,7 +92,7 @@ macro_rules! distr_duration { ($fnn:ident, $distr:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; b.iter(|| { @@ -114,7 +114,7 @@ macro_rules! distr { ($fnn:ident, $ty:ty, $distr:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; b.iter(|| { @@ -191,7 +191,7 @@ macro_rules! gen_range_int { ($fnn:ident, $ty:ident, $low:expr, $high:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); b.iter(|| { let mut high = $high; @@ -199,7 +199,7 @@ macro_rules! gen_range_int { for _ in 0..RAND_BENCH_N { accum = accum.wrapping_add(rng.gen_range($low..high)); // force recalculation of range each time - high = high.wrapping_add(1) & core::$ty::MAX; + high = high.wrapping_add(1) & $ty::MAX; } accum }); @@ -230,7 +230,7 @@ macro_rules! gen_range_float { ($fnn:ident, $ty:ident, $low:expr, $high:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); b.iter(|| { let mut high = $high; @@ -267,7 +267,7 @@ macro_rules! uniform_sample { ($fnn:ident, $type:ident, $low:expr, $high:expr, $count:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let low = black_box($low); let high = black_box($high); b.iter(|| { @@ -286,7 +286,7 @@ macro_rules! uniform_inclusive { ($fnn:ident, $type:ident, $low:expr, $high:expr, $count:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let low = black_box($low); let high = black_box($high); b.iter(|| { @@ -306,7 +306,7 @@ macro_rules! uniform_single { ($fnn:ident, $type:ident, $low:expr, $high:expr, $count:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let low = black_box($low); let high = black_box($high); b.iter(|| { diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index 9d3012fccbe..05090c24498 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -15,13 +15,14 @@ const RAND_BENCH_N: u64 = 1000; const BYTES_LEN: usize = 1024; use core::mem::size_of; +use rand_chacha::rand_core::UnwrapErr; use test::{black_box, Bencher}; use rand::prelude::*; use rand::rngs::ReseedingRng; use rand::rngs::{mock::StepRng, OsRng}; use rand_chacha::{ChaCha12Rng, ChaCha20Core, ChaCha20Rng, ChaCha8Rng}; -use rand_pcg::{Pcg32, Pcg64, Pcg64Mcg, Pcg64Dxsm}; +use rand_pcg::{Pcg32, Pcg64, Pcg64Dxsm, Pcg64Mcg}; macro_rules! gen_bytes { ($fnn:ident, $gen:expr) => { @@ -41,17 +42,17 @@ macro_rules! gen_bytes { } gen_bytes!(gen_bytes_step, StepRng::new(0, 1)); -gen_bytes!(gen_bytes_pcg32, Pcg32::from_entropy()); -gen_bytes!(gen_bytes_pcg64, Pcg64::from_entropy()); -gen_bytes!(gen_bytes_pcg64mcg, Pcg64Mcg::from_entropy()); -gen_bytes!(gen_bytes_pcg64dxsm, Pcg64Dxsm::from_entropy()); -gen_bytes!(gen_bytes_chacha8, ChaCha8Rng::from_entropy()); -gen_bytes!(gen_bytes_chacha12, ChaCha12Rng::from_entropy()); -gen_bytes!(gen_bytes_chacha20, ChaCha20Rng::from_entropy()); -gen_bytes!(gen_bytes_std, StdRng::from_entropy()); +gen_bytes!(gen_bytes_pcg32, Pcg32::from_os_rng()); +gen_bytes!(gen_bytes_pcg64, Pcg64::from_os_rng()); +gen_bytes!(gen_bytes_pcg64mcg, Pcg64Mcg::from_os_rng()); +gen_bytes!(gen_bytes_pcg64dxsm, Pcg64Dxsm::from_os_rng()); +gen_bytes!(gen_bytes_chacha8, ChaCha8Rng::from_os_rng()); +gen_bytes!(gen_bytes_chacha12, ChaCha12Rng::from_os_rng()); +gen_bytes!(gen_bytes_chacha20, ChaCha20Rng::from_os_rng()); +gen_bytes!(gen_bytes_std, StdRng::from_os_rng()); #[cfg(feature = "small_rng")] gen_bytes!(gen_bytes_small, SmallRng::from_thread_rng()); -gen_bytes!(gen_bytes_os, OsRng); +gen_bytes!(gen_bytes_os, UnwrapErr(OsRng)); gen_bytes!(gen_bytes_thread, thread_rng()); macro_rules! gen_uint { @@ -62,7 +63,7 @@ macro_rules! gen_uint { b.iter(|| { let mut accum: $ty = 0; for _ in 0..RAND_BENCH_N { - accum = accum.wrapping_add(rng.gen::<$ty>()); + accum = accum.wrapping_add(rng.random::<$ty>()); } accum }); @@ -72,40 +73,40 @@ macro_rules! gen_uint { } gen_uint!(gen_u32_step, u32, StepRng::new(0, 1)); -gen_uint!(gen_u32_pcg32, u32, Pcg32::from_entropy()); -gen_uint!(gen_u32_pcg64, u32, Pcg64::from_entropy()); -gen_uint!(gen_u32_pcg64mcg, u32, Pcg64Mcg::from_entropy()); -gen_uint!(gen_u32_pcg64dxsm, u32, Pcg64Dxsm::from_entropy()); -gen_uint!(gen_u32_chacha8, u32, ChaCha8Rng::from_entropy()); -gen_uint!(gen_u32_chacha12, u32, ChaCha12Rng::from_entropy()); -gen_uint!(gen_u32_chacha20, u32, ChaCha20Rng::from_entropy()); -gen_uint!(gen_u32_std, u32, StdRng::from_entropy()); +gen_uint!(gen_u32_pcg32, u32, Pcg32::from_os_rng()); +gen_uint!(gen_u32_pcg64, u32, Pcg64::from_os_rng()); +gen_uint!(gen_u32_pcg64mcg, u32, Pcg64Mcg::from_os_rng()); +gen_uint!(gen_u32_pcg64dxsm, u32, Pcg64Dxsm::from_os_rng()); +gen_uint!(gen_u32_chacha8, u32, ChaCha8Rng::from_os_rng()); +gen_uint!(gen_u32_chacha12, u32, ChaCha12Rng::from_os_rng()); +gen_uint!(gen_u32_chacha20, u32, ChaCha20Rng::from_os_rng()); +gen_uint!(gen_u32_std, u32, StdRng::from_os_rng()); #[cfg(feature = "small_rng")] gen_uint!(gen_u32_small, u32, SmallRng::from_thread_rng()); -gen_uint!(gen_u32_os, u32, OsRng); +gen_uint!(gen_u32_os, u32, UnwrapErr(OsRng)); gen_uint!(gen_u32_thread, u32, thread_rng()); gen_uint!(gen_u64_step, u64, StepRng::new(0, 1)); -gen_uint!(gen_u64_pcg32, u64, Pcg32::from_entropy()); -gen_uint!(gen_u64_pcg64, u64, Pcg64::from_entropy()); -gen_uint!(gen_u64_pcg64mcg, u64, Pcg64Mcg::from_entropy()); -gen_uint!(gen_u64_pcg64dxsm, u64, Pcg64Dxsm::from_entropy()); -gen_uint!(gen_u64_chacha8, u64, ChaCha8Rng::from_entropy()); -gen_uint!(gen_u64_chacha12, u64, ChaCha12Rng::from_entropy()); -gen_uint!(gen_u64_chacha20, u64, ChaCha20Rng::from_entropy()); -gen_uint!(gen_u64_std, u64, StdRng::from_entropy()); +gen_uint!(gen_u64_pcg32, u64, Pcg32::from_os_rng()); +gen_uint!(gen_u64_pcg64, u64, Pcg64::from_os_rng()); +gen_uint!(gen_u64_pcg64mcg, u64, Pcg64Mcg::from_os_rng()); +gen_uint!(gen_u64_pcg64dxsm, u64, Pcg64Dxsm::from_os_rng()); +gen_uint!(gen_u64_chacha8, u64, ChaCha8Rng::from_os_rng()); +gen_uint!(gen_u64_chacha12, u64, ChaCha12Rng::from_os_rng()); +gen_uint!(gen_u64_chacha20, u64, ChaCha20Rng::from_os_rng()); +gen_uint!(gen_u64_std, u64, StdRng::from_os_rng()); #[cfg(feature = "small_rng")] gen_uint!(gen_u64_small, u64, SmallRng::from_thread_rng()); -gen_uint!(gen_u64_os, u64, OsRng); +gen_uint!(gen_u64_os, u64, UnwrapErr(OsRng)); gen_uint!(gen_u64_thread, u64, thread_rng()); macro_rules! init_gen { ($fnn:ident, $gen:ident) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = Pcg32::from_entropy(); + let mut rng = Pcg32::from_os_rng(); b.iter(|| { - let r2 = $gen::from_rng(&mut rng).unwrap(); + let r2 = $gen::from_rng(&mut rng); r2 }); } @@ -125,7 +126,7 @@ macro_rules! reseeding_bytes { ($fnn:ident, $thresh:expr) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = ReseedingRng::new(ChaCha20Core::from_entropy(), $thresh * 1024, OsRng); + let mut rng = ReseedingRng::new(ChaCha20Core::from_os_rng(), $thresh * 1024, OsRng); let mut buf = [0u8; RESEEDING_BYTES_LEN]; b.iter(|| { for _ in 0..RESEEDING_BENCH_N { diff --git a/benches/benches/misc.rs b/benches/benches/misc.rs index a3c353bbdd6..50dfc0ea8a7 100644 --- a/benches/benches/misc.rs +++ b/benches/benches/misc.rs @@ -20,7 +20,7 @@ use rand_pcg::{Pcg32, Pcg64Mcg}; #[bench] fn misc_gen_bool_const(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg32::from_rng(&mut thread_rng()); b.iter(|| { let mut accum = true; for _ in 0..RAND_BENCH_N { @@ -32,7 +32,7 @@ fn misc_gen_bool_const(b: &mut Bencher) { #[bench] fn misc_gen_bool_var(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg32::from_rng(&mut thread_rng()); b.iter(|| { let mut accum = true; let mut p = 0.18; @@ -46,7 +46,7 @@ fn misc_gen_bool_var(b: &mut Bencher) { #[bench] fn misc_gen_ratio_const(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg32::from_rng(&mut thread_rng()); b.iter(|| { let mut accum = true; for _ in 0..RAND_BENCH_N { @@ -58,7 +58,7 @@ fn misc_gen_ratio_const(b: &mut Bencher) { #[bench] fn misc_gen_ratio_var(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg32::from_rng(&mut thread_rng()); b.iter(|| { let mut accum = true; for i in 2..(RAND_BENCH_N as u32 + 2) { @@ -70,7 +70,7 @@ fn misc_gen_ratio_var(b: &mut Bencher) { #[bench] fn misc_bernoulli_const(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg32::from_rng(&mut thread_rng()); b.iter(|| { let d = Bernoulli::new(0.18).unwrap(); let mut accum = true; @@ -83,7 +83,7 @@ fn misc_bernoulli_const(b: &mut Bencher) { #[bench] fn misc_bernoulli_var(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg32::from_rng(&mut thread_rng()); b.iter(|| { let mut accum = true; let mut p = 0.18; @@ -99,7 +99,7 @@ fn misc_bernoulli_var(b: &mut Bencher) { #[bench] fn gen_1kb_u16_iter_repeat(b: &mut Bencher) { use core::iter; - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); b.iter(|| { let v: Vec = iter::repeat(()).map(|()| rng.random()).take(512).collect(); v @@ -109,7 +109,7 @@ fn gen_1kb_u16_iter_repeat(b: &mut Bencher) { #[bench] fn gen_1kb_u16_sample_iter(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); b.iter(|| { let v: Vec = Standard.sample_iter(&mut rng).take(512).collect(); v @@ -119,7 +119,7 @@ fn gen_1kb_u16_sample_iter(b: &mut Bencher) { #[bench] fn gen_1kb_u16_gen_array(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); b.iter(|| { // max supported array length is 32! let v: [[u16; 32]; 16] = rng.random(); @@ -130,7 +130,7 @@ fn gen_1kb_u16_gen_array(b: &mut Bencher) { #[bench] fn gen_1kb_u16_fill(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); let mut buf = [0u16; 512]; b.iter(|| { rng.fill(&mut buf[..]); @@ -142,7 +142,7 @@ fn gen_1kb_u16_fill(b: &mut Bencher) { #[bench] fn gen_1kb_u64_iter_repeat(b: &mut Bencher) { use core::iter; - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); b.iter(|| { let v: Vec = iter::repeat(()).map(|()| rng.random()).take(128).collect(); v @@ -152,7 +152,7 @@ fn gen_1kb_u64_iter_repeat(b: &mut Bencher) { #[bench] fn gen_1kb_u64_sample_iter(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); b.iter(|| { let v: Vec = Standard.sample_iter(&mut rng).take(128).collect(); v @@ -162,7 +162,7 @@ fn gen_1kb_u64_sample_iter(b: &mut Bencher) { #[bench] fn gen_1kb_u64_gen_array(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); b.iter(|| { // max supported array length is 32! let v: [[u64; 32]; 4] = rng.random(); @@ -173,7 +173,7 @@ fn gen_1kb_u64_gen_array(b: &mut Bencher) { #[bench] fn gen_1kb_u64_fill(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()).unwrap(); + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); let mut buf = [0u64; 128]; b.iter(|| { rng.fill(&mut buf[..]); diff --git a/benches/benches/seq.rs b/benches/benches/seq.rs index bcc1e7c20e4..8b4b774b028 100644 --- a/benches/benches/seq.rs +++ b/benches/benches/seq.rs @@ -25,7 +25,7 @@ const RAND_BENCH_N: u64 = 1000; #[bench] fn seq_shuffle_100(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + let mut rng = SmallRng::from_rng(thread_rng()); let x: &mut [usize] = &mut [1; 100]; b.iter(|| { x.shuffle(&mut rng); @@ -35,7 +35,7 @@ fn seq_shuffle_100(b: &mut Bencher) { #[bench] fn seq_slice_choose_1_of_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + let mut rng = SmallRng::from_rng(thread_rng()); let x: &mut [usize] = &mut [1; 1000]; for (i, r) in x.iter_mut().enumerate() { *r = i; @@ -54,7 +54,7 @@ macro_rules! seq_slice_choose_multiple { ($name:ident, $amount:expr, $length:expr) => { #[bench] fn $name(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + let mut rng = SmallRng::from_rng(thread_rng()); let x: &[i32] = &[$amount; $length]; let mut result = [0i32; $amount]; b.iter(|| { @@ -76,14 +76,14 @@ seq_slice_choose_multiple!(seq_slice_choose_multiple_90_of_100, 90, 100); #[bench] fn seq_iter_choose_multiple_10_of_100(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + let mut rng = SmallRng::from_rng(thread_rng()); let x: &[usize] = &[1; 100]; b.iter(|| x.iter().cloned().choose_multiple(&mut rng, 10)) } #[bench] fn seq_iter_choose_multiple_fill_10_of_100(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + let mut rng = SmallRng::from_rng(thread_rng()); let x: &[usize] = &[1; 100]; let mut buf = [0; 10]; b.iter(|| x.iter().cloned().choose_multiple_fill(&mut rng, &mut buf)) @@ -93,7 +93,7 @@ macro_rules! sample_indices { ($name:ident, $fn:ident, $amount:expr, $length:expr) => { #[bench] fn $name(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + let mut rng = SmallRng::from_rng(thread_rng()); b.iter(|| index::$fn(&mut rng, $length, $amount)) } }; @@ -112,7 +112,7 @@ macro_rules! sample_indices_rand_weights { ($name:ident, $amount:expr, $length:expr) => { #[bench] fn $name(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); + let mut rng = SmallRng::from_rng(thread_rng()); b.iter(|| { index::sample_weighted(&mut rng, $length, |idx| (1 + (idx % 100)) as u32, $amount) }) diff --git a/benches/src/distributions.rs b/benches/src/distributions.rs index 2677fca4812..37b0c8d272b 100644 --- a/benches/src/distributions.rs +++ b/benches/src/distributions.rs @@ -30,7 +30,7 @@ macro_rules! distr_int { $group.throughput(Throughput::Bytes( size_of::<$ty>() as u64 * RAND_BENCH_N)); $group.bench_function($fnn, |c| { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; c.iter(|| { @@ -50,7 +50,7 @@ macro_rules! distr_float { $group.throughput(Throughput::Bytes( size_of::<$ty>() as u64 * RAND_BENCH_N)); $group.bench_function($fnn, |c| { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; c.iter(|| { @@ -70,7 +70,7 @@ macro_rules! distr { $group.throughput(Throughput::Bytes( size_of::<$ty>() as u64 * RAND_BENCH_N)); $group.bench_function($fnn, |c| { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; c.iter(|| { @@ -90,7 +90,7 @@ macro_rules! distr_arr { $group.throughput(Throughput::Bytes( size_of::<$ty>() as u64 * RAND_BENCH_N)); $group.bench_function($fnn, |c| { - let mut rng = Pcg64Mcg::from_entropy(); + let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; c.iter(|| { @@ -127,8 +127,9 @@ fn bench(c: &mut Criterion) { distr_float!(g, "log_normal", f64, LogNormal::new(-1.23, 4.56).unwrap()); g.throughput(Throughput::Bytes(size_of::() as u64 * RAND_BENCH_N)); g.bench_function("iter", |c| { - let mut rng = Pcg64Mcg::from_entropy(); - let distr = Normal::new(-2.71828, 3.14159).unwrap(); + use core::f64::consts::{E, PI}; + let mut rng = Pcg64Mcg::from_os_rng(); + let distr = Normal::new(-E, PI).unwrap(); let mut iter = distr.sample_iter(&mut rng); c.iter(|| { @@ -176,9 +177,9 @@ fn bench(c: &mut Criterion) { { let mut g = c.benchmark_group("weighted"); - distr_int!(g, "weighted_i8", usize, WeightedIndex::new(&[1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); - distr_int!(g, "weighted_u32", usize, WeightedIndex::new(&[1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); - distr_int!(g, "weighted_f64", usize, WeightedIndex::new(&[1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); + distr_int!(g, "weighted_i8", usize, WeightedIndex::new([1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "weighted_u32", usize, WeightedIndex::new([1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "weighted_f64", usize, WeightedIndex::new([1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); distr_int!(g, "weighted_large_set", usize, WeightedIndex::new((0..10000).rev().chain(1..10001)).unwrap()); distr_int!(g, "weighted_alias_method_i8", usize, WeightedAliasIndex::new(vec![1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); distr_int!(g, "weighted_alias_method_u32", usize, WeightedAliasIndex::new(vec![1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); @@ -194,7 +195,7 @@ fn bench(c: &mut Criterion) { sample_binomial!(g, "binomial_10", 10, 0.9); sample_binomial!(g, "binomial_100", 100, 0.99); sample_binomial!(g, "binomial_1000", 1000, 0.01); - sample_binomial!(g, "binomial_1e12", 1000_000_000_000, 0.2); + sample_binomial!(g, "binomial_1e12", 1_000_000_000_000, 0.2); } { diff --git a/benches/src/uniform.rs b/benches/src/uniform.rs index 0ed0f2cde40..948d1315889 100644 --- a/benches/src/uniform.rs +++ b/benches/src/uniform.rs @@ -23,8 +23,8 @@ const N_RESAMPLES: usize = 10_000; macro_rules! sample { ($R:ty, $T:ty, $U:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($R), "single"), |b| { - let mut rng = <$R>::from_rng(thread_rng()).unwrap(); - let x = rng.gen::<$U>(); + let mut rng = <$R>::from_rng(thread_rng()); + let x = rng.random::<$U>(); let bits = (<$T>::BITS / 2); let mask = (1 as $U).wrapping_neg() >> bits; let range = (x >> bits) * (x & mask); @@ -35,8 +35,8 @@ macro_rules! sample { }); $g.bench_function(BenchmarkId::new(stringify!($R), "distr"), |b| { - let mut rng = <$R>::from_rng(thread_rng()).unwrap(); - let x = rng.gen::<$U>(); + let mut rng = <$R>::from_rng(thread_rng()); + let x = rng.random::<$U>(); let bits = (<$T>::BITS / 2); let mask = (1 as $U).wrapping_neg() >> bits; let range = (x >> bits) * (x & mask); diff --git a/benches/src/uniform_float.rs b/benches/src/uniform_float.rs index 957ff1b8ecf..91c0eff4b7b 100644 --- a/benches/src/uniform_float.rs +++ b/benches/src/uniform_float.rs @@ -27,11 +27,11 @@ const N_RESAMPLES: usize = 10_000; macro_rules! single_random { ($R:ty, $T:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { - let mut rng = <$R>::from_rng(thread_rng()).unwrap(); + let mut rng = <$R>::from_rng(thread_rng()); let (mut low, mut high); loop { - low = <$T>::from_bits(rng.gen()); - high = <$T>::from_bits(rng.gen()); + low = <$T>::from_bits(rng.random()); + high = <$T>::from_bits(rng.random()); if (low < high) && (high - low).is_normal() { break; } @@ -63,10 +63,10 @@ fn single_random(c: &mut Criterion) { macro_rules! distr_random { ($R:ty, $T:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { - let mut rng = <$R>::from_rng(thread_rng()).unwrap(); + let mut rng = <$R>::from_rng(thread_rng()); let dist = loop { - let low = <$T>::from_bits(rng.gen()); - let high = <$T>::from_bits(rng.gen()); + let low = <$T>::from_bits(rng.random()); + let high = <$T>::from_bits(rng.random()); if let Ok(dist) = Uniform::<$T>::new_inclusive(low, high) { break dist; } diff --git a/rand_chacha/README.md b/rand_chacha/README.md index 6412538eadb..1b555ad086b 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -36,7 +36,7 @@ Links: `rand_chacha` is `no_std` compatible when disabling default features; the `std` feature can be explicitly required to re-enable `std` support. Using `std` allows detection of CPU features and thus better optimisation. Using `std` -also enables `getrandom` functionality, such as `ChaCha20Rng::from_entropy()`. +also enables `getrandom` functionality, such as `ChaCha20Rng::from_os_rng()`. # License diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index ebc28a8ab04..14be765a18b 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -14,9 +14,10 @@ use self::core::fmt; use crate::guts::ChaCha; use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; -use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; +use rand_core::{CryptoRng, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] use serde::{Serialize, Deserialize, Serializer, Deserializer}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; // NB. this must remain consistent with some currently hard-coded numbers in this module const BUF_BLOCKS: u8 = 4; @@ -68,7 +69,7 @@ impl fmt::Debug for Array64 { } macro_rules! chacha_impl { - ($ChaChaXCore:ident, $ChaChaXRng:ident, $rounds:expr, $doc:expr, $abst:ident) => { + ($ChaChaXCore:ident, $ChaChaXRng:ident, $rounds:expr, $doc:expr, $abst:ident,) => { #[doc=$doc] #[derive(Clone, PartialEq, Eq)] pub struct $ChaChaXCore { @@ -85,6 +86,7 @@ macro_rules! chacha_impl { impl BlockRngCore for $ChaChaXCore { type Item = u32; type Results = Array64; + #[inline] fn generate(&mut self, r: &mut Self::Results) { self.state.refill4($rounds, &mut r.0); @@ -93,9 +95,12 @@ macro_rules! chacha_impl { impl SeedableRng for $ChaChaXCore { type Seed = [u8; 32]; + #[inline] fn from_seed(seed: Self::Seed) -> Self { - $ChaChaXCore { state: ChaCha::new(&seed, &[0u8; 8]) } + $ChaChaXCore { + state: ChaCha::new(&seed, &[0u8; 8]), + } } } @@ -146,6 +151,7 @@ macro_rules! chacha_impl { impl SeedableRng for $ChaChaXRng { type Seed = [u8; 32]; + #[inline] fn from_seed(seed: Self::Seed) -> Self { let core = $ChaChaXCore::from_seed(seed); @@ -160,18 +166,16 @@ macro_rules! chacha_impl { fn next_u32(&mut self) -> u32 { self.rng.next_u32() } + #[inline] fn next_u64(&mut self) -> u64 { self.rng.next_u64() } + #[inline] fn fill_bytes(&mut self, bytes: &mut [u8]) { self.rng.fill_bytes(bytes) } - #[inline] - fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), Error> { - self.rng.try_fill_bytes(bytes) - } } impl $ChaChaXRng { @@ -209,11 +213,9 @@ macro_rules! chacha_impl { #[inline] pub fn set_word_pos(&mut self, word_offset: u128) { let block = (word_offset / u128::from(BLOCK_WORDS)) as u64; + self.rng.core.state.set_block_pos(block); self.rng - .core - .state - .set_block_pos(block); - self.rng.generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); + .generate_and_set((word_offset % u128::from(BLOCK_WORDS)) as usize); } /// Set the stream number. @@ -229,10 +231,7 @@ macro_rules! chacha_impl { /// indirectly via `set_word_pos`), but this is not directly supported. #[inline] pub fn set_stream(&mut self, stream: u64) { - self.rng - .core - .state - .set_nonce(stream); + self.rng.core.state.set_nonce(stream); if self.rng.index() != 64 { let wp = self.get_word_pos(); self.set_word_pos(wp); @@ -242,24 +241,20 @@ macro_rules! chacha_impl { /// Get the stream number. #[inline] pub fn get_stream(&self) -> u64 { - self.rng - .core - .state - .get_nonce() + self.rng.core.state.get_nonce() } /// Get the seed. #[inline] pub fn get_seed(&self) -> [u8; 32] { - self.rng - .core - .state - .get_seed() + self.rng.core.state.get_seed() } } impl CryptoRng for $ChaChaXRng {} + rand_core::impl_try_crypto_rng_from_crypto_rng!($ChaChaXRng); + impl From<$ChaChaXCore> for $ChaChaXRng { fn from(core: $ChaChaXCore) -> Self { $ChaChaXRng { @@ -286,22 +281,20 @@ macro_rules! chacha_impl { } #[cfg(feature = "serde1")] impl<'de> Deserialize<'de> for $ChaChaXRng { - fn deserialize(d: D) -> Result where D: Deserializer<'de> { + fn deserialize(d: D) -> Result + where D: Deserializer<'de> { $abst::$ChaChaXRng::deserialize(d).map(|x| Self::from(&x)) } } mod $abst { - #[cfg(feature = "serde1")] use serde::{Serialize, Deserialize}; + #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; // The abstract state of a ChaCha stream, independent of implementation choices. The // comparison and serialization of this object is considered a semver-covered part of // the API. #[derive(Debug, PartialEq, Eq)] - #[cfg_attr( - feature = "serde1", - derive(Serialize, Deserialize), - )] + #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub(crate) struct $ChaChaXRng { seed: [u8; 32], stream: u64, @@ -331,18 +324,36 @@ macro_rules! chacha_impl { } } } - } + }; } -chacha_impl!(ChaCha20Core, ChaCha20Rng, 10, "ChaCha with 20 rounds", abstract20); -chacha_impl!(ChaCha12Core, ChaCha12Rng, 6, "ChaCha with 12 rounds", abstract12); -chacha_impl!(ChaCha8Core, ChaCha8Rng, 4, "ChaCha with 8 rounds", abstract8); +chacha_impl!( + ChaCha20Core, + ChaCha20Rng, + 10, + "ChaCha with 20 rounds", + abstract20, +); +chacha_impl!( + ChaCha12Core, + ChaCha12Rng, + 6, + "ChaCha with 12 rounds", + abstract12, +); +chacha_impl!( + ChaCha8Core, + ChaCha8Rng, + 4, + "ChaCha with 8 rounds", + abstract8, +); #[cfg(test)] mod test { use rand_core::{RngCore, SeedableRng}; - #[cfg(feature = "serde1")] use super::{ChaCha20Rng, ChaCha12Rng, ChaCha8Rng}; + #[cfg(feature = "serde1")] use super::{ChaCha12Rng, ChaCha20Rng, ChaCha8Rng}; type ChaChaRng = super::ChaCha20Rng; @@ -350,8 +361,8 @@ mod test { #[test] fn test_chacha_serde_roundtrip() { let seed = [ - 1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, 0, 0, 0, 0, - 0, 2, 92, + 1, 0, 52, 0, 0, 0, 0, 0, 1, 0, 10, 0, 22, 32, 0, 0, 2, 0, 55, 49, 0, 11, 0, 0, 3, 0, 0, + 0, 0, 0, 2, 92, ]; let mut rng1 = ChaCha20Rng::from_seed(seed); let mut rng2 = ChaCha12Rng::from_seed(seed); @@ -388,7 +399,7 @@ mod test { #[test] fn test_chacha_serde_format_stability() { let j = r#"{"seed":[4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8],"stream":27182818284,"word_pos":314159265359}"#; - let r: ChaChaRng = serde_json::from_str(&j).unwrap(); + let r: ChaChaRng = serde_json::from_str(j).unwrap(); let j1 = serde_json::to_string(&r).unwrap(); assert_eq!(j, j1); } @@ -402,7 +413,7 @@ mod test { let mut rng1 = ChaChaRng::from_seed(seed); assert_eq!(rng1.next_u32(), 137206642); - let mut rng2 = ChaChaRng::from_rng(rng1).unwrap(); + let mut rng2 = ChaChaRng::from_rng(&mut rng1); assert_eq!(rng2.next_u32(), 1325750369); } @@ -598,7 +609,7 @@ mod test { #[test] fn test_chacha_word_pos_wrap_exact() { - use super::{BUF_BLOCKS, BLOCK_WORDS}; + use super::{BLOCK_WORDS, BUF_BLOCKS}; let mut rng = ChaChaRng::from_seed(Default::default()); // refilling the buffer in set_word_pos will wrap the block counter to 0 let last_block = (1 << 68) - u128::from(BUF_BLOCKS * BLOCK_WORDS); diff --git a/rand_core/src/blanket_impls.rs b/rand_core/src/blanket_impls.rs new file mode 100644 index 00000000000..cadd456ca5c --- /dev/null +++ b/rand_core/src/blanket_impls.rs @@ -0,0 +1,91 @@ +#[cfg(feature = "alloc")] use alloc::boxed::Box; + +use crate::{CryptoRng, RngCore, TryCryptoRng, TryRngCore}; + +impl<'a, R: RngCore + ?Sized> RngCore for &'a mut R { + #[inline(always)] + fn next_u32(&mut self) -> u32 { + R::next_u32(self) + } + + #[inline(always)] + fn next_u64(&mut self) -> u64 { + R::next_u64(self) + } + + #[inline(always)] + fn fill_bytes(&mut self, dst: &mut [u8]) { + R::fill_bytes(self, dst) + } +} + +impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {} + +impl<'a, R: TryRngCore + ?Sized> TryRngCore for &'a mut R { + type Error = R::Error; + + #[inline(always)] + fn try_next_u32(&mut self) -> Result { + R::try_next_u32(self) + } + + #[inline(always)] + fn try_next_u64(&mut self) -> Result { + R::try_next_u64(self) + } + + #[inline(always)] + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { + R::try_fill_bytes(self, dst) + } +} + +impl<'a, R: TryCryptoRng + ?Sized> TryCryptoRng for &'a mut R {} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +impl RngCore for Box { + #[inline(always)] + fn next_u32(&mut self) -> u32 { + R::next_u32(self) + } + + #[inline(always)] + fn next_u64(&mut self) -> u64 { + R::next_u64(self) + } + + #[inline(always)] + fn fill_bytes(&mut self, dest: &mut [u8]) { + R::fill_bytes(self, dest) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +impl CryptoRng for Box {} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +impl TryRngCore for Box { + type Error = R::Error; + + #[inline(always)] + fn try_next_u32(&mut self) -> Result { + R::try_next_u32(self) + } + + #[inline(always)] + fn try_next_u64(&mut self) -> Result { + R::try_next_u64(self) + } + + #[inline(always)] + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { + R::try_fill_bytes(self, dst) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] +impl TryCryptoRng for Box {} diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 9122f9bc675..b5cc42bdbef 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -54,10 +54,9 @@ //! [`fill_bytes`]: RngCore::fill_bytes use crate::impls::{fill_via_u32_chunks, fill_via_u64_chunks}; -use crate::{Error, CryptoRng, RngCore, SeedableRng}; +use crate::{CryptoRng, RngCore, SeedableRng, TryRngCore}; use core::fmt; -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// A trait for RNGs which do not generate random numbers individually, but in /// blocks (typically `[u32; N]`). This technique is commonly used by @@ -80,7 +79,7 @@ pub trait BlockRngCore { /// supposed to be cryptographically secure. /// /// See [`CryptoRng`] docs for more information. -pub trait CryptoBlockRng: BlockRngCore { } +pub trait CryptoBlockRng: BlockRngCore {} /// A wrapper type implementing [`RngCore`] for some type implementing /// [`BlockRngCore`] with `u32` array buffer; i.e. this can be used to implement @@ -97,16 +96,15 @@ pub trait CryptoBlockRng: BlockRngCore { } /// `BlockRng` has heavily optimized implementations of the [`RngCore`] methods /// reading values from the results buffer, as well as /// calling [`BlockRngCore::generate`] directly on the output array when -/// [`fill_bytes`] / [`try_fill_bytes`] is called on a large array. These methods -/// also handle the bookkeeping of when to generate a new batch of values. +/// [`fill_bytes`] is called on a large array. These methods also handle +/// the bookkeeping of when to generate a new batch of values. /// /// No whole generated `u32` values are thrown away and all values are consumed /// in-order. [`next_u32`] simply takes the next available `u32` value. /// [`next_u64`] is implemented by combining two `u32` values, least -/// significant first. [`fill_bytes`] and [`try_fill_bytes`] consume a whole -/// number of `u32` values, converting each `u32` to a byte slice in -/// little-endian order. If the requested byte length is not a multiple of 4, -/// some bytes will be discarded. +/// significant first. [`fill_bytes`] consume a whole number of `u32` values, +/// converting each `u32` to a byte slice in little-endian order. If the requested byte +/// length is not a multiple of 4, some bytes will be discarded. /// /// See also [`BlockRng64`] which uses `u64` array buffers. Currently there is /// no direct support for other buffer types. @@ -116,7 +114,6 @@ pub trait CryptoBlockRng: BlockRngCore { } /// [`next_u32`]: RngCore::next_u32 /// [`next_u64`]: RngCore::next_u64 /// [`fill_bytes`]: RngCore::fill_bytes -/// [`try_fill_bytes`]: RngCore::try_fill_bytes #[derive(Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr( @@ -227,19 +224,15 @@ impl> RngCore for BlockRng { if self.index >= self.results.as_ref().len() { self.generate_and_set(0); } - let (consumed_u32, filled_u8) = - fill_via_u32_chunks(&mut self.results.as_mut()[self.index..], &mut dest[read_len..]); + let (consumed_u32, filled_u8) = fill_via_u32_chunks( + &mut self.results.as_mut()[self.index..], + &mut dest[read_len..], + ); self.index += consumed_u32; read_len += filled_u8; } } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) - } } impl SeedableRng for BlockRng { @@ -256,8 +249,13 @@ impl SeedableRng for BlockRng { } #[inline(always)] - fn from_rng(rng: S) -> Result { - Ok(Self::new(R::from_rng(rng)?)) + fn from_rng(rng: impl RngCore) -> Self { + Self::new(R::from_rng(rng)) + } + + #[inline(always)] + fn try_from_rng(rng: S) -> Result { + R::try_from_rng(rng).map(Self::new) } } @@ -277,14 +275,12 @@ impl> CryptoRng for BlockRng {} /// then the other half is then consumed, however both [`next_u64`] and /// [`fill_bytes`] discard the rest of any half-consumed `u64`s when called. /// -/// [`fill_bytes`] and [`try_fill_bytes`] consume a whole number of `u64` -/// values. If the requested length is not a multiple of 8, some bytes will be -/// discarded. +/// [`fill_bytes`] `] consume a whole number of `u64` values. If the requested length +/// is not a multiple of 8, some bytes will be discarded. /// /// [`next_u32`]: RngCore::next_u32 /// [`next_u64`]: RngCore::next_u64 /// [`fill_bytes`]: RngCore::fill_bytes -/// [`try_fill_bytes`]: RngCore::try_fill_bytes #[derive(Clone)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct BlockRng64 { @@ -402,12 +398,6 @@ impl> RngCore for BlockRng64 { read_len += filled_u8; } } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) - } } impl SeedableRng for BlockRng64 { @@ -424,8 +414,13 @@ impl SeedableRng for BlockRng64 { } #[inline(always)] - fn from_rng(rng: S) -> Result { - Ok(Self::new(R::from_rng(rng)?)) + fn from_rng(rng: impl RngCore) -> Self { + Self::new(R::from_rng(rng)) + } + + #[inline(always)] + fn try_from_rng(rng: S) -> Result { + R::try_from_rng(rng).map(Self::new) } } @@ -433,8 +428,8 @@ impl> CryptoRng for BlockRng64 { #[cfg(test)] mod test { - use crate::{SeedableRng, RngCore}; use crate::block::{BlockRng, BlockRng64, BlockRngCore}; + use crate::{RngCore, SeedableRng}; #[derive(Debug, Clone)] struct DummyRng { @@ -443,7 +438,6 @@ mod test { impl BlockRngCore for DummyRng { type Item = u32; - type Results = [u32; 16]; fn generate(&mut self, results: &mut Self::Results) { @@ -458,7 +452,9 @@ mod test { type Seed = [u8; 4]; fn from_seed(seed: Self::Seed) -> Self { - DummyRng { counter: u32::from_le_bytes(seed) } + DummyRng { + counter: u32::from_le_bytes(seed), + } } } @@ -493,7 +489,6 @@ mod test { impl BlockRngCore for DummyRng64 { type Item = u64; - type Results = [u64; 8]; fn generate(&mut self, results: &mut Self::Results) { @@ -508,7 +503,9 @@ mod test { type Seed = [u8; 8]; fn from_seed(seed: Self::Seed) -> Self { - DummyRng64 { counter: u64::from_le_bytes(seed) } + DummyRng64 { + counter: u64::from_le_bytes(seed), + } } } diff --git a/rand_core/src/error.rs b/rand_core/src/error.rs deleted file mode 100644 index 1a5092fe82b..00000000000 --- a/rand_core/src/error.rs +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Error types - -use core::fmt; -use core::num::NonZeroU32; - -#[cfg(feature = "std")] use std::boxed::Box; - -/// Error type of random number generators -/// -/// In order to be compatible with `std` and `no_std`, this type has two -/// possible implementations: with `std` a boxed `Error` trait object is stored, -/// while with `no_std` we merely store an error code. -pub struct Error { - #[cfg(feature = "std")] - inner: Box, - #[cfg(not(feature = "std"))] - code: NonZeroU32, -} - -impl Error { - /// Codes at or above this point can be used by users to define their own - /// custom errors. - /// - /// This has a fixed value of `(1 << 31) + (1 << 30) = 0xC000_0000`, - /// therefore the number of values available for custom codes is `1 << 30`. - /// - /// This is identical to [`getrandom::Error::CUSTOM_START`](https://docs.rs/getrandom/latest/getrandom/struct.Error.html#associatedconstant.CUSTOM_START). - pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30); - /// Codes below this point represent OS Errors (i.e. positive i32 values). - /// Codes at or above this point, but below [`Error::CUSTOM_START`] are - /// reserved for use by the `rand` and `getrandom` crates. - /// - /// This is identical to [`getrandom::Error::INTERNAL_START`](https://docs.rs/getrandom/latest/getrandom/struct.Error.html#associatedconstant.INTERNAL_START). - pub const INTERNAL_START: u32 = 1 << 31; - - /// Construct from any type supporting `std::error::Error` - /// - /// Available only when configured with `std`. - /// - /// See also `From`, which is available with and without `std`. - #[cfg(feature = "std")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - #[inline] - pub fn new(err: E) -> Self - where E: Into> { - Error { inner: err.into() } - } - - /// Reference the inner error (`std` only) - /// - /// When configured with `std`, this is a trivial operation and never - /// panics. Without `std`, this method is simply unavailable. - #[cfg(feature = "std")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - #[inline] - pub fn inner(&self) -> &(dyn std::error::Error + Send + Sync + 'static) { - &*self.inner - } - - /// Unwrap the inner error (`std` only) - /// - /// When configured with `std`, this is a trivial operation and never - /// panics. Without `std`, this method is simply unavailable. - #[cfg(feature = "std")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] - #[inline] - pub fn take_inner(self) -> Box { - self.inner - } - - /// Extract the raw OS error code (if this error came from the OS) - /// - /// This method is identical to `std::io::Error::raw_os_error()`, except - /// that it works in `no_std` contexts. If this method returns `None`, the - /// error value can still be formatted via the `Display` implementation. - #[inline] - pub fn raw_os_error(&self) -> Option { - #[cfg(feature = "std")] - { - if let Some(e) = self.inner.downcast_ref::() { - return e.raw_os_error(); - } - } - match self.code() { - Some(code) if u32::from(code) < Self::INTERNAL_START => Some(u32::from(code) as i32), - _ => None, - } - } - - /// Retrieve the error code, if any. - /// - /// If this `Error` was constructed via `From`, then this method - /// will return this `NonZeroU32` code (for `no_std` this is always the - /// case). Otherwise, this method will return `None`. - #[inline] - pub fn code(&self) -> Option { - #[cfg(feature = "std")] - { - self.inner.downcast_ref::().map(|c| c.0) - } - #[cfg(not(feature = "std"))] - { - Some(self.code) - } - } -} - -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "std")] - { - write!(f, "Error {{ inner: {:?} }}", self.inner) - } - #[cfg(all(feature = "getrandom", not(feature = "std")))] - { - getrandom::Error::from(self.code).fmt(f) - } - #[cfg(not(any(feature = "getrandom", feature = "std")))] - { - write!(f, "Error {{ code: {} }}", self.code) - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "std")] - { - write!(f, "{}", self.inner) - } - #[cfg(all(feature = "getrandom", not(feature = "std")))] - { - getrandom::Error::from(self.code).fmt(f) - } - #[cfg(not(any(feature = "getrandom", feature = "std")))] - { - write!(f, "error code {}", self.code) - } - } -} - -impl From for Error { - #[inline] - fn from(code: NonZeroU32) -> Self { - #[cfg(feature = "std")] - { - Error { - inner: Box::new(ErrorCode(code)), - } - } - #[cfg(not(feature = "std"))] - { - Error { code } - } - } -} - -#[cfg(feature = "getrandom")] -impl From for Error { - #[inline] - fn from(error: getrandom::Error) -> Self { - #[cfg(feature = "std")] - { - Error { - inner: Box::new(error), - } - } - #[cfg(not(feature = "std"))] - { - Error { code: error.code() } - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error { - #[inline] - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.inner.source() - } -} - -#[cfg(feature = "std")] -impl From for std::io::Error { - #[inline] - fn from(error: Error) -> Self { - if let Some(code) = error.raw_os_error() { - std::io::Error::from_raw_os_error(code) - } else { - std::io::Error::new(std::io::ErrorKind::Other, error) - } - } -} - -#[cfg(feature = "std")] -#[derive(Debug, Copy, Clone)] -struct ErrorCode(NonZeroU32); - -#[cfg(feature = "std")] -impl fmt::Display for ErrorCode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "error code {}", self.0) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ErrorCode {} - -#[cfg(test)] -mod test { - #[cfg(feature = "getrandom")] - #[test] - fn test_error_codes() { - // Make sure the values are the same as in `getrandom`. - assert_eq!(super::Error::CUSTOM_START, getrandom::Error::CUSTOM_START); - assert_eq!(super::Error::INTERNAL_START, getrandom::Error::INTERNAL_START); - } -} diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index d7dcd3457d3..f9152fb2734 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -74,7 +74,7 @@ impl Observable for u64 { /// unaltered. fn fill_via_chunks(src: &mut [T], dest: &mut [u8]) -> (usize, usize) { let size = core::mem::size_of::(); - let byte_len = min(src.len() * size, dest.len()); + let byte_len = min(core::mem::size_of_val(src), dest.len()); let num_chunks = (byte_len + size - 1) / size; // Byte-swap for portability of results. This must happen before copying @@ -160,6 +160,56 @@ pub fn next_u64_via_fill(rng: &mut R) -> u64 { u64::from_le_bytes(buf) } +/// Implement [`TryRngCore`] for a type implementing [`RngCore`]. +/// +/// Ideally, `rand_core` would define blanket impls for this, but they conflict with blanket impls +/// for `&mut R` and `Box`, so until specialziation is stabilized, implementer crates +/// have to implement `TryRngCore` directly. +#[macro_export] +macro_rules! impl_try_rng_from_rng_core { + ($t:ty) => { + impl $crate::TryRngCore for $t { + type Error = core::convert::Infallible; + + #[inline] + fn try_next_u32(&mut self) -> Result { + Ok($crate::RngCore::next_u32(self)) + } + + #[inline] + fn try_next_u64(&mut self) -> Result { + Ok($crate::RngCore::next_u64(self)) + } + + #[inline] + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { + $crate::RngCore::fill_bytes(self, dst); + Ok(()) + } + } + }; +} + +/// Implement [`TryCryptoRng`] and [`TryRngCore`] for a type implementing [`CryptoRng`]. +/// +/// Ideally, `rand_core` would define blanket impls for this, but they conflict with blanket impls +/// for `&mut R` and `Box`, so until specialziation is stabilized, implementer crates +/// have to implement `TryRngCore` and `TryCryptoRng` directly. +#[macro_export] +macro_rules! impl_try_crypto_rng_from_crypto_rng { + ($t:ty) => { + $crate::impl_try_rng_from_rng_core!($t); + + impl $crate::TryCryptoRng for $t {} + + /// Check at compile time that `$t` implements `CryptoRng` + const _: () = { + const fn check_crypto_rng_impl() {} + check_crypto_rng_impl::<$t>(); + }; + }; +} + #[cfg(test)] mod test { use super::*; diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 6617ea1c803..07e2b4b5c04 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -19,9 +19,6 @@ //! [`SeedableRng`] is an extension trait for construction from fixed seeds and //! other random number generators. //! -//! [`Error`] is provided for error-handling. It is safe to use in `no_std` -//! environments. -//! //! The [`impls`] and [`le`] sub-modules include a few small functions to assist //! implementation of [`RngCore`]. //! @@ -40,18 +37,18 @@ #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "std")] extern crate std; -#[cfg(feature = "alloc")] use alloc::boxed::Box; - -pub use error::Error; -#[cfg(feature = "getrandom")] pub use os::OsRng; +use core::fmt; +mod blanket_impls; pub mod block; -mod error; pub mod impls; pub mod le; #[cfg(feature = "getrandom")] mod os; +#[cfg(feature = "getrandom")] pub use getrandom; +#[cfg(feature = "getrandom")] pub use os::OsRng; + /// The core of a random number generator. /// @@ -68,11 +65,6 @@ pub mod le; /// [`next_u32`] and [`next_u64`] methods, implementations may discard some /// random bits for efficiency. /// -/// The [`try_fill_bytes`] method is a variant of [`fill_bytes`] allowing error -/// handling; it is not deemed sufficiently useful to add equivalents for -/// [`next_u32`] or [`next_u64`] since the latter methods are almost always used -/// with algorithmic generators (PRNGs), which are normally infallible. -/// /// Implementers should produce bits uniformly. Pathological RNGs (e.g. always /// returning the same value, or never setting certain bits) can break rejection /// sampling used by random distributions, and also break other RNGs when @@ -87,6 +79,10 @@ pub mod le; /// in this trait directly, then use the helper functions from the /// [`impls`] module to implement the other methods. /// +/// Implementors of [`RngCore`] SHOULD also implement the [`TryRngCore`] +/// trait with the `Error` associated type being equal to [`Infallible`]. +/// It can be done using the [`impl_try_rng_from_rng_core!`] macro. +/// /// It is recommended that implementations also implement: /// /// - `Debug` with a custom implementation which *does not* print any internal @@ -107,7 +103,7 @@ pub mod le; /// /// ``` /// #![allow(dead_code)] -/// use rand_core::{RngCore, Error, impls}; +/// use rand_core::{RngCore, impls}; /// /// struct CountingRng(u64); /// @@ -121,21 +117,19 @@ pub mod le; /// self.0 /// } /// -/// fn fill_bytes(&mut self, dest: &mut [u8]) { -/// impls::fill_bytes_via_next(self, dest) -/// } -/// -/// fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { -/// Ok(self.fill_bytes(dest)) +/// fn fill_bytes(&mut self, dst: &mut [u8]) { +/// impls::fill_bytes_via_next(self, dst) /// } /// } +/// +/// rand_core::impl_try_rng_from_rng_core!(CountingRng); /// ``` /// /// [`rand`]: https://docs.rs/rand -/// [`try_fill_bytes`]: RngCore::try_fill_bytes /// [`fill_bytes`]: RngCore::fill_bytes /// [`next_u32`]: RngCore::next_u32 /// [`next_u64`]: RngCore::next_u64 +/// [`Infallible`]: core::convert::Infallible pub trait RngCore { /// Return the next random `u32`. /// @@ -155,42 +149,22 @@ pub trait RngCore { /// /// RNGs must implement at least one method from this trait directly. In /// the case this method is not implemented directly, it can be implemented - /// via [`impls::fill_bytes_via_next`] or - /// via [`RngCore::try_fill_bytes`]; if this generator can - /// fail the implementation must choose how best to handle errors here - /// (e.g. panic with a descriptive message or log a warning and retry a few - /// times). + /// via [`impls::fill_bytes_via_next`]. /// /// This method should guarantee that `dest` is entirely filled /// with new data, and may panic if this is impossible /// (e.g. reading past the end of a file that is being used as the /// source of randomness). - fn fill_bytes(&mut self, dest: &mut [u8]); - - /// Fill `dest` entirely with random data. - /// - /// This is the only method which allows an RNG to report errors while - /// generating random data thus making this the primary method implemented - /// by external (true) RNGs (e.g. `OsRng`) which can fail. It may be used - /// directly to generate keys and to seed (infallible) PRNGs. - /// - /// Other than error handling, this method is identical to [`RngCore::fill_bytes`]; - /// thus this may be implemented using `Ok(self.fill_bytes(dest))` or - /// `fill_bytes` may be implemented with - /// `self.try_fill_bytes(dest).unwrap()` or more specific error handling. - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error>; - - /// Convert an [`RngCore`] to a [`RngReadAdapter`]. - #[cfg(feature = "std")] - fn read_adapter(&mut self) -> RngReadAdapter<'_, Self> - where Self: Sized { - RngReadAdapter { inner: self } - } + fn fill_bytes(&mut self, dst: &mut [u8]); } /// A marker trait used to indicate that an [`RngCore`] implementation is /// supposed to be cryptographically secure. /// +/// Implementors of [`CryptoRng`] SHOULD also implement the [`TryCryptoRng`] +/// trait with the `Error` associated type being equal to [`Infallible`]. +/// It can be done using the [`impl_try_crypto_rng_from_crypto_rng!`] macro. +/// /// *Cryptographically secure generators*, also known as *CSPRNGs*, should /// satisfy an additional properties over other generators: given the first /// *k* bits of an algorithm's output @@ -210,8 +184,74 @@ pub trait RngCore { /// weaknesses such as seeding from a weak entropy source or leaking state. /// /// [`BlockRngCore`]: block::BlockRngCore +/// [`Infallible`]: core::convert::Infallible pub trait CryptoRng: RngCore {} +/// A potentially fallible version of [`RngCore`]. +/// +/// This trait is primarily used for IO-based generators such as [`OsRng`]. +/// +/// Most of higher-level generic code in the `rand` crate is built on top +/// of the the [`RngCore`] trait. Users can transform a fallible RNG +/// (i.e. [`TryRngCore`] implementor) into an "infallible" (but potentially +/// panicking) RNG (i.e. [`RngCore`] implementor) using the [`UnwrapErr`] wrapper. +/// +/// [`RngCore`] implementors also usually implement [`TryRngCore`] with the `Error` +/// associated type being equal to [`Infallible`][core::convert::Infallible]. +/// In other words, users can use [`TryRngCore`] to generalize over fallible and +/// infallible RNGs. +pub trait TryRngCore { + /// The type returned in the event of a RNG error. + type Error: fmt::Debug + fmt::Display; + + /// Return the next random `u32`. + fn try_next_u32(&mut self) -> Result; + /// Return the next random `u64`. + fn try_next_u64(&mut self) -> Result; + /// Fill `dest` entirely with random data. + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error>; + + /// Wrap RNG with the [`UnwrapErr`] wrapper. + fn unwrap_err(self) -> UnwrapErr + where Self: Sized { + UnwrapErr(self) + } + + /// Convert an [`RngCore`] to a [`RngReadAdapter`]. + #[cfg(feature = "std")] + fn read_adapter(&mut self) -> RngReadAdapter<'_, Self> + where Self: Sized { + RngReadAdapter { inner: self } + } +} + +/// A marker trait used to indicate that a [`TryRngCore`] implementation is +/// supposed to be cryptographically secure. +/// +/// See [`CryptoRng`] docs for more information about cryptographically secure generators. +pub trait TryCryptoRng: TryRngCore {} + +/// Wrapper around [`TryRngCore`] implementation which implements [`RngCore`] +/// by panicking on potential errors. +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)] +pub struct UnwrapErr(pub R); + +impl RngCore for UnwrapErr { + fn next_u32(&mut self) -> u32 { + self.0.try_next_u32().unwrap() + } + + fn next_u64(&mut self) -> u64 { + self.0.try_next_u64().unwrap() + } + + fn fill_bytes(&mut self, dst: &mut [u8]) { + self.0.try_fill_bytes(dst).unwrap() + } +} + +impl CryptoRng for UnwrapErr {} + /// A random number generator that can be explicitly seeded. /// /// This trait encapsulates the low-level functionality common to all @@ -339,7 +379,7 @@ pub trait SeedableRng: Sized { Self::from_seed(seed) } - /// Create a new PRNG seeded from another `Rng`. + /// Create a new PRNG seeded from an infallible `Rng`. /// /// This may be useful when needing to rapidly seed many PRNGs from a master /// PRNG, and to allow forking of PRNGs. It may be considered deterministic. @@ -363,7 +403,16 @@ pub trait SeedableRng: Sized { /// (in prior versions this was not required). /// /// [`rand`]: https://docs.rs/rand - fn from_rng(mut rng: R) -> Result { + fn from_rng(mut rng: impl RngCore) -> Self { + let mut seed = Self::Seed::default(); + rng.fill_bytes(seed.as_mut()); + Self::from_seed(seed) + } + + /// Create a new PRNG seeded from a potentially fallible `Rng`. + /// + /// See [`from_rng`][SeedableRng::from_rng] docs for more infromation. + fn try_from_rng(mut rng: R) -> Result { let mut seed = Self::Seed::default(); rng.try_fill_bytes(seed.as_mut())?; Ok(Self::from_seed(seed)) @@ -374,6 +423,9 @@ pub trait SeedableRng: Sized { /// This method is the recommended way to construct non-deterministic PRNGs /// since it is convenient and secure. /// + /// Note that this method may panic on (extremely unlikely) [`getrandom`] errors. + /// If it's not desirable, use the [`try_from_os_rng`] method instead. + /// /// In case the overhead of using [`getrandom`] to seed *many* PRNGs is an /// issue, one may prefer to seed from a local PRNG, e.g. /// `from_rng(thread_rng()).unwrap()`. @@ -383,66 +435,32 @@ pub trait SeedableRng: Sized { /// If [`getrandom`] is unable to provide secure entropy this method will panic. /// /// [`getrandom`]: https://docs.rs/getrandom + /// [`try_from_os_rng`]: SeedableRng::try_from_os_rng #[cfg(feature = "getrandom")] #[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] #[track_caller] - fn from_entropy() -> Self { - let mut seed = Self::Seed::default(); - if let Err(err) = getrandom::getrandom(seed.as_mut()) { - panic!("from_entropy failed: {}", err); + fn from_os_rng() -> Self { + match Self::try_from_os_rng() { + Ok(res) => res, + Err(err) => panic!("from_os_rng failed: {}", err), } - Self::from_seed(seed) - } -} - -// Implement `RngCore` for references to an `RngCore`. -// Force inlining all functions, so that it is up to the `RngCore` -// implementation and the optimizer to decide on inlining. -impl<'a, R: RngCore + ?Sized> RngCore for &'a mut R { - #[inline(always)] - fn next_u32(&mut self) -> u32 { - (**self).next_u32() - } - - #[inline(always)] - fn next_u64(&mut self) -> u64 { - (**self).next_u64() } - #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { - (**self).fill_bytes(dest) - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - (**self).try_fill_bytes(dest) - } -} - -// Implement `RngCore` for boxed references to an `RngCore`. -// Force inlining all functions, so that it is up to the `RngCore` -// implementation and the optimizer to decide on inlining. -#[cfg(feature = "alloc")] -impl RngCore for Box { - #[inline(always)] - fn next_u32(&mut self) -> u32 { - (**self).next_u32() - } - - #[inline(always)] - fn next_u64(&mut self) -> u64 { - (**self).next_u64() - } - - #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { - (**self).fill_bytes(dest) - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - (**self).try_fill_bytes(dest) + /// Creates a new instance of the RNG seeded via [`getrandom`] without unwrapping + /// potential [`getrandom`] errors. + /// + /// In case the overhead of using [`getrandom`] to seed *many* PRNGs is an + /// issue, one may prefer to seed from a local PRNG, e.g. + /// `from_rng(&mut thread_rng()).unwrap()`. + /// + /// [`getrandom`]: https://docs.rs/getrandom + #[cfg(feature = "getrandom")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] + fn try_from_os_rng() -> Result { + let mut seed = Self::Seed::default(); + getrandom::getrandom(seed.as_mut())?; + let res = Self::from_seed(seed); + Ok(res) } } @@ -453,38 +471,32 @@ impl RngCore for Box { /// ```no_run /// # use std::{io, io::Read}; /// # use std::fs::File; -/// # use rand_core::{OsRng, RngCore}; +/// # use rand_core::{OsRng, TryRngCore}; /// /// io::copy(&mut OsRng.read_adapter().take(100), &mut File::create("/tmp/random.bytes").unwrap()).unwrap(); /// ``` #[cfg(feature = "std")] -pub struct RngReadAdapter<'a, R: RngCore + ?Sized> { +pub struct RngReadAdapter<'a, R: TryRngCore + ?Sized> { inner: &'a mut R, } #[cfg(feature = "std")] -impl std::io::Read for RngReadAdapter<'_, R> { +impl std::io::Read for RngReadAdapter<'_, R> { fn read(&mut self, buf: &mut [u8]) -> Result { - self.inner.try_fill_bytes(buf)?; + self.inner.try_fill_bytes(buf).map_err(|err| { + std::io::Error::new(std::io::ErrorKind::Other, std::format!("RNG error: {err}")) + })?; Ok(buf.len()) } } #[cfg(feature = "std")] -impl std::fmt::Debug for RngReadAdapter<'_, R> { +impl std::fmt::Debug for RngReadAdapter<'_, R> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ReadAdapter").finish() } } -// Implement `CryptoRng` for references to a `CryptoRng`. -impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {} - -// Implement `CryptoRng` for boxed references to a `CryptoRng`. -#[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] -impl CryptoRng for Box {} - #[cfg(test)] mod test { use super::*; diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index b43c9fdaf05..7d0a8ce0adb 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -8,7 +8,7 @@ //! Interface to the random number generator of the operating system. -use crate::{impls, CryptoRng, Error, RngCore}; +use crate::{TryCryptoRng, TryRngCore}; use getrandom::getrandom; /// A random number generator that retrieves randomness from the @@ -36,11 +36,11 @@ use getrandom::getrandom; /// /// # Usage example /// ``` -/// use rand_core::{RngCore, OsRng}; +/// use rand_core::{TryRngCore, OsRng}; /// /// let mut key = [0u8; 16]; -/// OsRng.fill_bytes(&mut key); -/// let random_u64 = OsRng.next_u64(); +/// OsRng.try_fill_bytes(&mut key).unwrap(); +/// let random_u64 = OsRng.try_next_u64().unwrap(); /// ``` /// /// [getrandom]: https://crates.io/crates/getrandom @@ -48,39 +48,41 @@ use getrandom::getrandom; #[derive(Clone, Copy, Debug, Default)] pub struct OsRng; -impl CryptoRng for OsRng {} +impl TryRngCore for OsRng { + type Error = getrandom::Error; -impl RngCore for OsRng { - fn next_u32(&mut self) -> u32 { - impls::next_u32_via_fill(self) + #[inline] + fn try_next_u32(&mut self) -> Result { + let mut buf = [0u8; 4]; + getrandom(&mut buf)?; + Ok(u32::from_ne_bytes(buf)) } - fn next_u64(&mut self) -> u64 { - impls::next_u64_via_fill(self) + #[inline] + fn try_next_u64(&mut self) -> Result { + let mut buf = [0u8; 8]; + getrandom(&mut buf)?; + Ok(u64::from_ne_bytes(buf)) } - fn fill_bytes(&mut self, dest: &mut [u8]) { - if let Err(e) = self.try_fill_bytes(dest) { - panic!("Error: {}", e); - } - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + #[inline] + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { getrandom(dest)?; Ok(()) } } +impl TryCryptoRng for OsRng {} + #[test] fn test_os_rng() { - let x = OsRng.next_u64(); - let y = OsRng.next_u64(); + let x = OsRng.try_next_u64().unwrap(); + let y = OsRng.try_next_u64().unwrap(); assert!(x != 0); assert!(x != y); } #[test] fn test_construction() { - let mut rng = OsRng::default(); - assert!(rng.next_u64() != 0); + assert!(OsRng.try_next_u64().unwrap() != 0); } diff --git a/rand_distr/tests/uniformity.rs b/rand_distr/tests/uniformity.rs index d37ef0a9d06..4818eac9907 100644 --- a/rand_distr/tests/uniformity.rs +++ b/rand_distr/tests/uniformity.rs @@ -23,7 +23,7 @@ fn unit_sphere() { let h = Histogram100::with_const_width(-1., 1.); let mut histograms = [h.clone(), h.clone(), h]; let dist = rand_distr::UnitSphere; - let mut rng = rand_pcg::Pcg32::from_entropy(); + let mut rng = rand_pcg::Pcg32::from_os_rng(); for _ in 0..N_SAMPLES { let v: [f64; 3] = dist.sample(&mut rng); for i in 0..N_DIM { @@ -51,7 +51,7 @@ fn unit_circle() { use core::f64::consts::PI; let mut h = Histogram100::with_const_width(-PI, PI); let dist = rand_distr::UnitCircle; - let mut rng = rand_pcg::Pcg32::from_entropy(); + let mut rng = rand_pcg::Pcg32::from_os_rng(); for _ in 0..N_SAMPLES { let v: [f64; 2] = dist.sample(&mut rng); h.add(v[0].atan2(v[1])).unwrap(); diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index e67728a90e6..72e68586839 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -47,7 +47,7 @@ //! use rand::{Rng, SeedableRng}; //! use rand_pcg::Pcg64Mcg; //! -//! let mut rng = Pcg64Mcg::from_entropy(); +//! let mut rng = Pcg64Mcg::from_os_rng(); //! let x: f64 = rng.gen(); //! ``` diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index df2025dc444..ecb0e56bc47 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -14,7 +14,7 @@ const MULTIPLIER: u128 = 0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645; use core::fmt; -use rand_core::{impls, le, Error, RngCore, SeedableRng}; +use rand_core::{impls, le, RngCore, SeedableRng}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// A PCG random number generator (XSL RR 128/64 (LCG) variant). @@ -151,12 +151,6 @@ impl RngCore for Lcg128Xsl64 { fn fill_bytes(&mut self, dest: &mut [u8]) { impls::fill_bytes_via_next(self, dest) } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) - } } @@ -261,12 +255,6 @@ impl RngCore for Mcg128Xsl64 { fn fill_bytes(&mut self, dest: &mut [u8]) { impls::fill_bytes_via_next(self, dest) } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) - } } #[inline(always)] diff --git a/rand_pcg/src/pcg128cm.rs b/rand_pcg/src/pcg128cm.rs index 7ac5187e4e0..29be17a904d 100644 --- a/rand_pcg/src/pcg128cm.rs +++ b/rand_pcg/src/pcg128cm.rs @@ -14,7 +14,7 @@ const MULTIPLIER: u64 = 15750249268501108917; use core::fmt; -use rand_core::{impls, le, Error, RngCore, SeedableRng}; +use rand_core::{impls, le, RngCore, SeedableRng}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// A PCG random number generator (CM DXSM 128/64 (LCG) variant). @@ -148,21 +148,15 @@ impl RngCore for Lcg128CmDxsm64 { #[inline] fn next_u64(&mut self) -> u64 { - let val = output_dxsm(self.state); + let res = output_dxsm(self.state); self.step(); - val + res } #[inline] fn fill_bytes(&mut self, dest: &mut [u8]) { impls::fill_bytes_via_next(self, dest) } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) - } } #[inline(always)] diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index 365f1c0b117..0b6864a42f3 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -11,7 +11,7 @@ //! PCG random number generators use core::fmt; -use rand_core::{impls, le, Error, RngCore, SeedableRng}; +use rand_core::{impls, le, RngCore, SeedableRng}; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; // This is the default multiplier used by PCG for 64-bit state. @@ -160,10 +160,4 @@ impl RngCore for Lcg64Xsh32 { fn fill_bytes(&mut self, dest: &mut [u8]) { impls::fill_bytes_via_next(self, dest) } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) - } } diff --git a/rand_pcg/tests/lcg128cmdxsm64.rs b/rand_pcg/tests/lcg128cmdxsm64.rs index b254b4fac66..d5f3a6a498d 100644 --- a/rand_pcg/tests/lcg128cmdxsm64.rs +++ b/rand_pcg/tests/lcg128cmdxsm64.rs @@ -23,7 +23,7 @@ fn test_lcg128cmdxsm64_construction() { let mut rng1 = Lcg128CmDxsm64::from_seed(seed); assert_eq!(rng1.next_u64(), 12201417210360370199); - let mut rng2 = Lcg128CmDxsm64::from_rng(&mut rng1).unwrap(); + let mut rng2 = Lcg128CmDxsm64::from_rng(&mut rng1); assert_eq!(rng2.next_u64(), 11487972556150888383); let mut rng3 = Lcg128CmDxsm64::seed_from_u64(0); diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index 31eada442eb..92c9a41f57d 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -23,7 +23,7 @@ fn test_lcg128xsl64_construction() { let mut rng1 = Lcg128Xsl64::from_seed(seed); assert_eq!(rng1.next_u64(), 8740028313290271629); - let mut rng2 = Lcg128Xsl64::from_rng(&mut rng1).unwrap(); + let mut rng2 = Lcg128Xsl64::from_rng(&mut rng1); assert_eq!(rng2.next_u64(), 1922280315005786345); let mut rng3 = Lcg128Xsl64::seed_from_u64(0); diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index 9c181ee3a45..ee884706fb4 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -21,7 +21,7 @@ fn test_lcg64xsh32_construction() { let mut rng1 = Lcg64Xsh32::from_seed(seed); assert_eq!(rng1.next_u64(), 1204678643940597513); - let mut rng2 = Lcg64Xsh32::from_rng(&mut rng1).unwrap(); + let mut rng2 = Lcg64Xsh32::from_rng(&mut rng1); assert_eq!(rng2.next_u64(), 12384929573776311845); let mut rng3 = Lcg64Xsh32::seed_from_u64(0); diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index 1f352b6e879..bee87aeb3f7 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -21,7 +21,7 @@ fn test_mcg128xsl64_construction() { let mut rng1 = Mcg128Xsl64::from_seed(seed); assert_eq!(rng1.next_u64(), 7071994460355047496); - let mut rng2 = Mcg128Xsl64::from_rng(&mut rng1).unwrap(); + let mut rng2 = Mcg128Xsl64::from_rng(&mut rng1); assert_eq!(rng2.next_u64(), 12300796107712034932); let mut rng3 = Mcg128Xsl64::seed_from_u64(0); diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index a69fa08510d..0eabfb80594 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -10,9 +10,8 @@ //! Distribution trait and associates use crate::Rng; +#[cfg(feature = "alloc")] use alloc::string::String; use core::iter; -#[cfg(feature = "alloc")] -use alloc::string::String; /// Types (distributions) that can be used to create a random instance of `T`. /// @@ -229,9 +228,7 @@ mod tests { #[test] fn test_make_an_iter() { - fn ten_dice_rolls_other_than_five( - rng: &mut R, - ) -> impl Iterator + '_ { + fn ten_dice_rolls_other_than_five(rng: &mut R) -> impl Iterator + '_ { Uniform::new_inclusive(1, 6) .unwrap() .sample_iter(rng) @@ -251,8 +248,8 @@ mod tests { #[test] #[cfg(feature = "alloc")] fn test_dist_string() { - use core::str; use crate::distributions::{Alphanumeric, DistString, Standard}; + use core::str; let mut rng = crate::test::rng(213); let s1 = Alphanumeric.sample_string(&mut rng, 20); diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 050e75d98ea..91303428875 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -191,7 +191,7 @@ use crate::Rng; /// use rand::prelude::*; /// use rand::distributions::Standard; /// -/// let val: f32 = StdRng::from_entropy().sample(Standard); +/// let val: f32 = StdRng::from_os_rng().sample(Standard); /// println!("f32 from [0, 1): {}", val); /// ``` /// diff --git a/src/lib.rs b/src/lib.rs index 5410b16f33b..af51387441a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,8 +57,8 @@ clippy::nonminimal_bool )] -#[cfg(feature = "std")] extern crate std; #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] extern crate std; #[allow(unused)] macro_rules! trace { ($($x:tt)*) => ( @@ -92,7 +92,7 @@ macro_rules! error { ($($x:tt)*) => ( ) } // Re-exports from rand_core -pub use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; +pub use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; // Public modules pub mod distributions; @@ -154,7 +154,10 @@ use crate::distributions::{Distribution, Standard}; /// [`Standard`]: distributions::Standard /// [`ThreadRng`]: rngs::ThreadRng #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] -#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))))] +#[cfg_attr( + doc_cfg, + doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))) +)] #[inline] pub fn random() -> T where Standard: Distribution { diff --git a/src/prelude.rs b/src/prelude.rs index 2f9fa4c8ff3..87613532f78 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -14,7 +14,7 @@ //! //! ``` //! use rand::prelude::*; -//! # let mut r = StdRng::from_rng(thread_rng()).unwrap(); +//! # let mut r = StdRng::from_rng(thread_rng()); //! # let _: f32 = r.random(); //! ``` @@ -23,7 +23,8 @@ #[doc(no_inline)] pub use crate::rngs::SmallRng; #[cfg(feature = "std_rng")] -#[doc(no_inline)] pub use crate::rngs::StdRng; +#[doc(no_inline)] +pub use crate::rngs::StdRng; #[doc(no_inline)] #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use crate::rngs::ThreadRng; diff --git a/src/rng.rs b/src/rng.rs index 6c9d4b7eabf..129c141796e 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -13,7 +13,7 @@ use crate::distributions::uniform::{SampleRange, SampleUniform}; use crate::distributions::{self, Distribution, Standard}; use core::num::Wrapping; use core::{mem, slice}; -use rand_core::{Error, RngCore}; +use rand_core::RngCore; /// An automatically-implemented extension trait on [`RngCore`] providing high-level /// generic methods for sampling values and other convenience methods. @@ -224,8 +224,6 @@ pub trait Rng: RngCore { /// The distribution is expected to be uniform with portable results, but /// this cannot be guaranteed for third-party implementations. /// - /// This is identical to [`try_fill`] except that it panics on error. - /// /// # Example /// /// ``` @@ -236,38 +234,9 @@ pub trait Rng: RngCore { /// ``` /// /// [`fill_bytes`]: RngCore::fill_bytes - /// [`try_fill`]: Rng::try_fill #[track_caller] fn fill(&mut self, dest: &mut T) { - dest.try_fill(self).expect("Rng::fill failed") - } - - /// Fill any type implementing [`Fill`] with random data - /// - /// The distribution is expected to be uniform with portable results, but - /// this cannot be guaranteed for third-party implementations. - /// - /// This is identical to [`fill`] except that it forwards errors. - /// - /// # Example - /// - /// ``` - /// # use rand::Error; - /// use rand::{thread_rng, Rng}; - /// - /// # fn try_inner() -> Result<(), Error> { - /// let mut arr = [0u64; 4]; - /// thread_rng().try_fill(&mut arr[..])?; - /// # Ok(()) - /// # } - /// - /// # try_inner().unwrap() - /// ``` - /// - /// [`try_fill_bytes`]: RngCore::try_fill_bytes - /// [`fill`]: Rng::fill - fn try_fill(&mut self, dest: &mut T) -> Result<(), Error> { - dest.try_fill(self) + dest.fill(self) } /// Return a bool with a probability `p` of being true. @@ -335,9 +304,12 @@ pub trait Rng: RngCore { /// Alias for [`Rng::random`]. #[inline] - #[deprecated(since="0.9.0", note="Renamed to `random` to avoid conflict with the new `gen` keyword in Rust 2024.")] + #[deprecated( + since = "0.9.0", + note = "Renamed to `random` to avoid conflict with the new `gen` keyword in Rust 2024." + )] fn gen(&mut self) -> T - where Standard: Distribution { + where Standard: Distribution { self.random() } } @@ -353,18 +325,17 @@ impl Rng for R {} /// [Chapter on Portability](https://rust-random.github.io/book/portability.html)). pub trait Fill { /// Fill self with random data - fn try_fill(&mut self, rng: &mut R) -> Result<(), Error>; + fn fill(&mut self, rng: &mut R); } macro_rules! impl_fill_each { () => {}; ($t:ty) => { impl Fill for [$t] { - fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { + fn fill(&mut self, rng: &mut R) { for elt in self.iter_mut() { *elt = rng.random(); } - Ok(()) } } }; @@ -377,8 +348,8 @@ macro_rules! impl_fill_each { impl_fill_each!(bool, char, f32, f64,); impl Fill for [u8] { - fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { - rng.try_fill_bytes(self) + fn fill(&mut self, rng: &mut R) { + rng.fill_bytes(self) } } @@ -387,37 +358,35 @@ macro_rules! impl_fill { ($t:ty) => { impl Fill for [$t] { #[inline(never)] // in micro benchmarks, this improves performance - fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { + fn fill(&mut self, rng: &mut R) { if self.len() > 0 { - rng.try_fill_bytes(unsafe { + rng.fill_bytes(unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut u8, mem::size_of_val(self) ) - })?; + }); for x in self { *x = x.to_le(); } } - Ok(()) } } impl Fill for [Wrapping<$t>] { #[inline(never)] - fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { + fn fill(&mut self, rng: &mut R) { if self.len() > 0 { - rng.try_fill_bytes(unsafe { + rng.fill_bytes(unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut u8, self.len() * mem::size_of::<$t>() ) - })?; + }); for x in self { *x = Wrapping(x.0.to_le()); } } - Ok(()) } } }; @@ -435,8 +404,8 @@ impl_fill!(i8, i16, i32, i64, isize, i128,); impl Fill for [T; N] where [T]: Fill { - fn try_fill(&mut self, rng: &mut R) -> Result<(), Error> { - self[..].try_fill(rng) + fn fill(&mut self, rng: &mut R) { + <[T] as Fill>::fill(self, rng) } } @@ -543,24 +512,23 @@ mod test { #[test] #[should_panic] + #[allow(clippy::reversed_empty_ranges)] fn test_gen_range_panic_int() { - #![allow(clippy::reversed_empty_ranges)] let mut r = rng(102); r.gen_range(5..-2); } #[test] #[should_panic] + #[allow(clippy::reversed_empty_ranges)] fn test_gen_range_panic_usize() { - #![allow(clippy::reversed_empty_ranges)] let mut r = rng(103); r.gen_range(5..2); } #[test] + #[allow(clippy::bool_assert_comparison)] fn test_gen_bool() { - #![allow(clippy::bool_assert_comparison)] - let mut r = rng(105); for _ in 0..5 { assert_eq!(r.gen_bool(0.0), false); @@ -568,6 +536,16 @@ mod test { } } + #[test] + fn test_rng_mut_ref() { + fn use_rng(mut r: impl Rng) { + let _ = r.next_u32(); + } + + let mut rng = rng(109); + use_rng(&mut rng); + } + #[test] fn test_rng_trait_object() { use crate::distributions::{Distribution, Standard}; diff --git a/src/rngs/mock.rs b/src/rngs/mock.rs index 66705a008cd..e186fb7f062 100644 --- a/src/rngs/mock.rs +++ b/src/rngs/mock.rs @@ -8,10 +8,9 @@ //! Mock random number generator -use rand_core::{impls, Error, RngCore}; +use rand_core::{impls, RngCore}; -#[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// A mock generator yielding very predictable output /// @@ -64,27 +63,22 @@ impl RngCore for StepRng { #[inline] fn next_u64(&mut self) -> u64 { - let result = self.v; + let res = self.v; self.v = self.v.wrapping_add(self.a); - result + res } #[inline] - fn fill_bytes(&mut self, dest: &mut [u8]) { - impls::fill_bytes_via_next(self, dest); - } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) + fn fill_bytes(&mut self, dst: &mut [u8]) { + impls::fill_bytes_via_next(self, dst) } } +rand_core::impl_try_rng_from_rng_core!(StepRng); + #[cfg(test)] mod tests { - #[cfg(any(feature = "alloc", feature = "serde1"))] - use super::StepRng; + #[cfg(any(feature = "alloc", feature = "serde1"))] use super::StepRng; #[test] #[cfg(feature = "serde1")] @@ -94,18 +88,16 @@ mod tests { bincode::deserialize(&bincode::serialize(&some_rng).unwrap()).unwrap(); assert_eq!(some_rng.v, de_some_rng.v); assert_eq!(some_rng.a, de_some_rng.a); - } #[test] #[cfg(feature = "alloc")] fn test_bool() { - use crate::{Rng, distributions::Standard}; + use crate::{distributions::Standard, Rng}; // If this result ever changes, update doc on StepRng! let rng = StepRng::new(0, 1 << 31); - let result: alloc::vec::Vec = - rng.sample_iter(Standard).take(6).collect(); + let result: alloc::vec::Vec = rng.sample_iter(Standard).take(6).collect(); assert_eq!(&result, &[false, true, false, true, false, true]); } } diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index 9caeb09e028..d357a715669 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -42,7 +42,7 @@ //! - `from_seed` accepts a type specific to the PRNG //! - `from_rng` allows a PRNG to be seeded from any other RNG //! - `seed_from_u64` allows any PRNG to be seeded from a `u64` insecurely -//! - `from_entropy` securely seeds a PRNG from fresh entropy +//! - `from_os_rng` securely seeds a PRNG from system randomness source //! //! Use the [`rand_core`] crate when implementing your own RNGs. //! @@ -102,18 +102,21 @@ pub use reseeding::ReseedingRng; pub mod mock; // Public so we don't export `StepRng` directly, making it a bit // more clear it is intended for testing. -#[cfg(all(feature = "small_rng", target_pointer_width = "64"))] -mod xoshiro256plusplus; +#[cfg(feature = "small_rng")] mod small; #[cfg(all(feature = "small_rng", not(target_pointer_width = "64")))] mod xoshiro128plusplus; -#[cfg(feature = "small_rng")] mod small; +#[cfg(all(feature = "small_rng", target_pointer_width = "64"))] +mod xoshiro256plusplus; #[cfg(feature = "std_rng")] mod std; -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub(crate) mod thread; +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +pub(crate) mod thread; #[cfg(feature = "small_rng")] pub use self::small::SmallRng; #[cfg(feature = "std_rng")] pub use self::std::StdRng; -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use self::thread::ThreadRng; +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +pub use self::thread::ThreadRng; #[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] -#[cfg(feature = "getrandom")] pub use rand_core::OsRng; +#[cfg(feature = "getrandom")] +pub use rand_core::OsRng; diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index 752952e67ea..a0076f959a6 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -12,8 +12,8 @@ use core::mem::size_of_val; -use rand_core::{CryptoRng, Error, RngCore, SeedableRng}; use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; +use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; /// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the /// ability to reseed it. @@ -59,7 +59,7 @@ use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; /// use rand::rngs::OsRng; /// use rand::rngs::ReseedingRng; /// -/// let prng = ChaCha20Core::from_entropy(); +/// let prng = ChaCha20Core::from_os_rng(); /// let mut reseeding_rng = ReseedingRng::new(prng, 0, OsRng); /// /// println!("{}", reseeding_rng.random::()); @@ -75,12 +75,12 @@ use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; pub struct ReseedingRng(BlockRng>) where R: BlockRngCore + SeedableRng, - Rsdr: RngCore; + Rsdr: TryRngCore; impl ReseedingRng where R: BlockRngCore + SeedableRng, - Rsdr: RngCore, + Rsdr: TryRngCore, { /// Create a new `ReseedingRng` from an existing PRNG, combined with a RNG /// to use as reseeder. @@ -95,7 +95,7 @@ where /// Immediately reseed the generator /// /// This discards any remaining random data in the cache. - pub fn reseed(&mut self) -> Result<(), Error> { + pub fn reseed(&mut self) -> Result<(), Rsdr::Error> { self.0.reset(); self.0.core.reseed() } @@ -103,9 +103,10 @@ where // TODO: this should be implemented for any type where the inner type // implements RngCore, but we can't specify that because ReseedingCore is private -impl RngCore for ReseedingRng +impl RngCore for ReseedingRng where R: BlockRngCore + SeedableRng, + Rsdr: TryRngCore, { #[inline(always)] fn next_u32(&mut self) -> u32 { @@ -120,16 +121,12 @@ where fn fill_bytes(&mut self, dest: &mut [u8]) { self.0.fill_bytes(dest) } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.0.try_fill_bytes(dest) - } } impl Clone for ReseedingRng where R: BlockRngCore + SeedableRng + Clone, - Rsdr: RngCore + Clone, + Rsdr: TryRngCore + Clone, { fn clone(&self) -> ReseedingRng { // Recreating `BlockRng` seems easier than cloning it and resetting @@ -141,7 +138,7 @@ where impl CryptoRng for ReseedingRng where R: BlockRngCore + SeedableRng + CryptoBlockRng, - Rsdr: CryptoRng, + Rsdr: TryCryptoRng, { } @@ -156,7 +153,7 @@ struct ReseedingCore { impl BlockRngCore for ReseedingCore where R: BlockRngCore + SeedableRng, - Rsdr: RngCore, + Rsdr: TryRngCore, { type Item = ::Item; type Results = ::Results; @@ -177,7 +174,7 @@ where impl ReseedingCore where R: BlockRngCore + SeedableRng, - Rsdr: RngCore, + Rsdr: TryRngCore, { /// Create a new `ReseedingCore`. fn new(rng: R, threshold: u64, reseeder: Rsdr) -> Self { @@ -202,8 +199,8 @@ where } /// Reseed the internal PRNG. - fn reseed(&mut self) -> Result<(), Error> { - R::from_rng(&mut self.reseeder).map(|result| { + fn reseed(&mut self) -> Result<(), Rsdr::Error> { + R::try_from_rng(&mut self.reseeder).map(|result| { self.bytes_until_reseed = self.threshold; self.inner = result }) @@ -228,7 +225,7 @@ where impl Clone for ReseedingCore where R: BlockRngCore + SeedableRng + Clone, - Rsdr: RngCore + Clone, + Rsdr: TryRngCore + Clone, { fn clone(&self) -> ReseedingCore { ReseedingCore { @@ -243,22 +240,23 @@ where impl CryptoBlockRng for ReseedingCore where R: BlockRngCore + SeedableRng + CryptoBlockRng, - Rsdr: CryptoRng, -{} + Rsdr: TryCryptoRng, +{ +} #[cfg(feature = "std_rng")] #[cfg(test)] mod test { - use crate::{Rng, SeedableRng}; use crate::rngs::mock::StepRng; use crate::rngs::std::Core; + use crate::{Rng, SeedableRng}; use super::ReseedingRng; #[test] fn test_reseeding() { let mut zero = StepRng::new(0, 0); - let rng = Core::from_rng(&mut zero).unwrap(); + let rng = Core::from_rng(&mut zero); let thresh = 1; // reseed every time the buffer is exhausted let mut reseeding = ReseedingRng::new(rng, thresh, zero); @@ -276,11 +274,10 @@ mod test { } #[test] + #[allow(clippy::redundant_clone)] fn test_clone_reseeding() { - #![allow(clippy::redundant_clone)] - let mut zero = StepRng::new(0, 0); - let rng = Core::from_rng(&mut zero).unwrap(); + let rng = Core::from_rng(&mut zero); let mut rng1 = ReseedingRng::new(rng, 32 * 4, zero); let first: u32 = rng1.random(); diff --git a/src/rngs/small.rs b/src/rngs/small.rs index 2841b0b5dd8..12276885f69 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -8,7 +8,7 @@ //! A small fast RNG -use rand_core::{Error, RngCore, SeedableRng}; +use rand_core::{RngCore, SeedableRng}; #[cfg(target_pointer_width = "64")] type Rng = super::xoshiro256plusplus::Xoshiro256PlusPlus; @@ -55,15 +55,12 @@ impl RngCore for SmallRng { #[inline(always)] fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest); - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.0.try_fill_bytes(dest) + self.0.fill_bytes(dest) } } +rand_core::impl_try_rng_from_rng_core!(SmallRng); + impl SmallRng { /// Construct an instance seeded from another `Rng` /// @@ -76,8 +73,8 @@ impl SmallRng { /// let rng = SmallRng::from_rng(rand::thread_rng()); /// ``` #[inline(always)] - pub fn from_rng(rng: R) -> Result { - Rng::from_rng(rng).map(SmallRng) + pub fn from_rng(rng: R) -> Self { + Self(Rng::from_rng(rng)) } /// Construct an instance seeded from the thread-local RNG diff --git a/src/rngs/std.rs b/src/rngs/std.rs index 3de125f3e5c..34a6fa8cdf6 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -8,7 +8,7 @@ //! The standard RNG -use crate::{CryptoRng, Error, RngCore, SeedableRng}; +use rand_core::{CryptoRng, RngCore, SeedableRng}; #[cfg(any(test, feature = "getrandom"))] pub(crate) use rand_chacha::ChaCha12Core as Core; @@ -20,7 +20,7 @@ use rand_chacha::ChaCha12Rng as Rng; /// (meaning a cryptographically secure PRNG). /// /// The current algorithm used is the ChaCha block cipher with 12 rounds. Please -/// see this relevant [rand issue] for the discussion. This may change as new +/// see this relevant [rand issue] for the discussion. This may change as new /// evidence of cipher security and performance becomes available. /// /// The algorithm is deterministic but should not be considered reproducible @@ -46,13 +46,8 @@ impl RngCore for StdRng { } #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest); - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.0.try_fill_bytes(dest) + fn fill_bytes(&mut self, dst: &mut [u8]) { + self.0.fill_bytes(dst) } } @@ -64,15 +59,11 @@ impl SeedableRng for StdRng { fn from_seed(seed: Self::Seed) -> Self { StdRng(Rng::from_seed(seed)) } - - #[inline(always)] - fn from_rng(rng: R) -> Result { - Rng::from_rng(rng).map(StdRng) - } } impl CryptoRng for StdRng {} +rand_core::impl_try_crypto_rng_from_crypto_rng!(StdRng); #[cfg(test)] mod test { @@ -92,7 +83,7 @@ mod test { let mut rng0 = StdRng::from_seed(seed); let x0 = rng0.next_u64(); - let mut rng1 = StdRng::from_rng(rng0).unwrap(); + let mut rng1 = StdRng::from_rng(&mut rng0); let x1 = rng1.next_u64(); assert_eq!([x0, x1], target); diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index f79ded83e35..08654149670 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -9,14 +9,15 @@ //! Thread-local random number generator use core::cell::UnsafeCell; +use std::fmt; use std::rc::Rc; use std::thread_local; -use std::fmt; + +use rand_core::{CryptoRng, RngCore, SeedableRng}; use super::std::Core; -use crate::rngs::ReseedingRng; use crate::rngs::OsRng; -use crate::{CryptoRng, Error, RngCore, SeedableRng}; +use crate::rngs::ReseedingRng; // Rationale for using `UnsafeCell` in `ThreadRng`: // @@ -76,7 +77,10 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// /// [`ReseedingRng`]: crate::rngs::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng -#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))))] +#[cfg_attr( + doc_cfg, + doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))) +)] #[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync @@ -87,7 +91,7 @@ impl ThreadRng { /// Immediately reseed the generator /// /// This discards any remaining random data in the cache. - pub fn reseed(&mut self) -> Result<(), Error> { + pub fn reseed(&mut self) -> Result<(), rand_core::getrandom::Error> { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference let rng = unsafe { &mut *self.rng.get() }; @@ -106,7 +110,7 @@ thread_local!( // We require Rc<..> to avoid premature freeing when thread_rng is used // within thread-local destructors. See #968. static THREAD_RNG_KEY: Rc>> = { - let r = Core::from_rng(OsRng).unwrap_or_else(|err| + let r = Core::try_from_os_rng().unwrap_or_else(|err| panic!("could not initialize thread_rng: {}", err)); let rng = ReseedingRng::new(r, THREAD_RNG_RESEED_THRESHOLD, @@ -132,7 +136,10 @@ thread_local!( /// println!("A simulated die roll: {}", rng.gen_range(1..=6)); /// # } /// ``` -#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))))] +#[cfg_attr( + doc_cfg, + doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))) +)] pub fn thread_rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng } @@ -161,23 +168,18 @@ impl RngCore for ThreadRng { rng.next_u64() } + #[inline(always)] fn fill_bytes(&mut self, dest: &mut [u8]) { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference let rng = unsafe { &mut *self.rng.get() }; rng.fill_bytes(dest) } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - // SAFETY: We must make sure to stop using `rng` before anyone else - // creates another mutable reference - let rng = unsafe { &mut *self.rng.get() }; - rng.try_fill_bytes(dest) - } } impl CryptoRng for ThreadRng {} +rand_core::impl_try_crypto_rng_from_crypto_rng!(ThreadRng); #[cfg(test)] mod test { @@ -193,6 +195,9 @@ mod test { fn test_debug_output() { // We don't care about the exact output here, but it must not include // private CSPRNG state or the cache stored by BlockRng! - assert_eq!(std::format!("{:?}", crate::thread_rng()), "ThreadRng { .. }"); + assert_eq!( + std::format!("{:?}", crate::thread_rng()), + "ThreadRng { .. }" + ); } } diff --git a/src/rngs/xoshiro128plusplus.rs b/src/rngs/xoshiro128plusplus.rs index ece98fafd6a..416ea91a9fa 100644 --- a/src/rngs/xoshiro128plusplus.rs +++ b/src/rngs/xoshiro128plusplus.rs @@ -6,10 +6,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#[cfg(feature="serde1")] use serde::{Serialize, Deserialize}; -use rand_core::impls::{next_u64_via_u32, fill_bytes_via_next}; +use rand_core::impls::{fill_bytes_via_next, next_u64_via_u32}; use rand_core::le::read_u32_into; -use rand_core::{SeedableRng, RngCore, Error}; +use rand_core::{RngCore, SeedableRng}; +#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// A xoshiro128++ random number generator. /// @@ -20,7 +20,7 @@ use rand_core::{SeedableRng, RngCore, Error}; /// reference source code](http://xoshiro.di.unimi.it/xoshiro128plusplus.c) by /// David Blackman and Sebastiano Vigna. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Xoshiro128PlusPlus { s: [u32; 4], } @@ -61,7 +61,7 @@ impl SeedableRng for Xoshiro128PlusPlus { impl RngCore for Xoshiro128PlusPlus { #[inline] fn next_u32(&mut self) -> u32 { - let result_starstar = self.s[0] + let res = self.s[0] .wrapping_add(self.s[3]) .rotate_left(7) .wrapping_add(self.s[0]); @@ -77,7 +77,7 @@ impl RngCore for Xoshiro128PlusPlus { self.s[3] = self.s[3].rotate_left(11); - result_starstar + res } #[inline] @@ -86,30 +86,27 @@ impl RngCore for Xoshiro128PlusPlus { } #[inline] - fn fill_bytes(&mut self, dest: &mut [u8]) { - fill_bytes_via_next(self, dest); - } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) + fn fill_bytes(&mut self, dst: &mut [u8]) { + fill_bytes_via_next(self, dst) } } +rand_core::impl_try_rng_from_rng_core!(Xoshiro128PlusPlus); + #[cfg(test)] mod tests { - use super::*; + use super::Xoshiro128PlusPlus; + use rand_core::{RngCore, SeedableRng}; #[test] fn reference() { - let mut rng = Xoshiro128PlusPlus::from_seed( - [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]); + let mut rng = + Xoshiro128PlusPlus::from_seed([1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]); // These values were produced with the reference implementation: // http://xoshiro.di.unimi.it/xoshiro128plusplus.c let expected = [ - 641, 1573767, 3222811527, 3517856514, 836907274, 4247214768, - 3867114732, 1355841295, 495546011, 621204420, + 641, 1573767, 3222811527, 3517856514, 836907274, 4247214768, 3867114732, 1355841295, + 495546011, 621204420, ]; for &e in &expected { assert_eq!(rng.next_u32(), e); diff --git a/src/rngs/xoshiro256plusplus.rs b/src/rngs/xoshiro256plusplus.rs index 8ffb18b8033..0fdc66df7c8 100644 --- a/src/rngs/xoshiro256plusplus.rs +++ b/src/rngs/xoshiro256plusplus.rs @@ -6,10 +6,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#[cfg(feature="serde1")] use serde::{Serialize, Deserialize}; use rand_core::impls::fill_bytes_via_next; use rand_core::le::read_u64_into; -use rand_core::{SeedableRng, RngCore, Error}; +use rand_core::{RngCore, SeedableRng}; +#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; /// A xoshiro256++ random number generator. /// @@ -20,7 +20,7 @@ use rand_core::{SeedableRng, RngCore, Error}; /// reference source code](http://xoshiro.di.unimi.it/xoshiro256plusplus.c) by /// David Blackman and Sebastiano Vigna. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature="serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Xoshiro256PlusPlus { s: [u64; 4], } @@ -63,12 +63,13 @@ impl RngCore for Xoshiro256PlusPlus { fn next_u32(&mut self) -> u32 { // The lowest bits have some linear dependencies, so we use the // upper bits instead. - (self.next_u64() >> 32) as u32 + let val = self.next_u64(); + (val >> 32) as u32 } #[inline] fn next_u64(&mut self) -> u64 { - let result_plusplus = self.s[0] + let res = self.s[0] .wrapping_add(self.s[3]) .rotate_left(23) .wrapping_add(self.s[0]); @@ -84,36 +85,41 @@ impl RngCore for Xoshiro256PlusPlus { self.s[3] = self.s[3].rotate_left(45); - result_plusplus + res } #[inline] - fn fill_bytes(&mut self, dest: &mut [u8]) { - fill_bytes_via_next(self, dest); - } - - #[inline] - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.fill_bytes(dest); - Ok(()) + fn fill_bytes(&mut self, dst: &mut [u8]) { + fill_bytes_via_next(self, dst) } } +rand_core::impl_try_rng_from_rng_core!(Xoshiro256PlusPlus); + #[cfg(test)] mod tests { - use super::*; + use super::Xoshiro256PlusPlus; + use rand_core::{RngCore, SeedableRng}; #[test] fn reference() { - let mut rng = Xoshiro256PlusPlus::from_seed( - [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, - 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]); + let mut rng = Xoshiro256PlusPlus::from_seed([ + 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, + 0, 0, 0, + ]); // These values were produced with the reference implementation: // http://xoshiro.di.unimi.it/xoshiro256plusplus.c let expected = [ - 41943041, 58720359, 3588806011781223, 3591011842654386, - 9228616714210784205, 9973669472204895162, 14011001112246962877, - 12406186145184390807, 15849039046786891736, 10450023813501588000, + 41943041, + 58720359, + 3588806011781223, + 3591011842654386, + 9228616714210784205, + 9973669472204895162, + 14011001112246962877, + 12406186145184390807, + 15849039046786891736, + 10450023813501588000, ]; for &e in &expected { assert_eq!(rng.next_u64(), e); From e93776960eaa3da167ba581b95ee3c26d1ed1186 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Wed, 8 May 2024 06:30:08 -0700 Subject: [PATCH 382/443] Update Panic documentation and #[track_caller] (#1447) This is stuff I missed in #1442 Signed-off-by: Joe Richey Co-authored-by: Diggory Hardy --- CHANGELOG.md | 2 +- rand_core/src/le.rs | 10 ++++++++++ rand_core/src/lib.rs | 1 - rand_distr/src/weighted_tree.rs | 1 + src/seq/index.rs | 1 + 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44994339514..583f162a78a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Bump the MSRV to 1.61.0 - Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) - Move all benchmarks to new `benches` crate (#1439) -- Annotate panicking methods with `#[track_caller]` (#1442) +- Annotate panicking methods with `#[track_caller]` (#1442, #1447) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/rand_core/src/le.rs b/rand_core/src/le.rs index 89e5e729c8a..92ba7d755c4 100644 --- a/rand_core/src/le.rs +++ b/rand_core/src/le.rs @@ -12,7 +12,12 @@ //! useful functions available. /// Reads unsigned 32 bit integers from `src` into `dst`. +/// +/// # Panics +/// +/// If `dst` has insufficent space (`4*dst.len() < src.len()`). #[inline] +#[track_caller] pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { assert!(src.len() >= 4 * dst.len()); for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(4)) { @@ -21,7 +26,12 @@ pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { } /// Reads unsigned 64 bit integers from `src` into `dst`. +/// +/// # Panics +/// +/// If `dst` has insufficent space (`8*dst.len() < src.len()`). #[inline] +#[track_caller] pub fn read_u64_into(src: &[u8], dst: &mut [u64]) { assert!(src.len() >= 8 * dst.len()); for (out, chunk) in dst.iter_mut().zip(src.chunks_exact(8)) { diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 07e2b4b5c04..d0c8af065c8 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -438,7 +438,6 @@ pub trait SeedableRng: Sized { /// [`try_from_os_rng`]: SeedableRng::try_from_os_rng #[cfg(feature = "getrandom")] #[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] - #[track_caller] fn from_os_rng() -> Self { match Self::try_from_os_rng() { Ok(res) => res, diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index e8a3681f3c9..c292578bf4e 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -290,6 +290,7 @@ impl + Weight> impl + Weight> Distribution for WeightedTreeIndex { + #[track_caller] fn sample(&self, rng: &mut R) -> usize { self.try_sample(rng).unwrap() } diff --git a/src/seq/index.rs b/src/seq/index.rs index e7b1e2b22f9..e34b1c2ca62 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -219,6 +219,7 @@ impl ExactSizeIterator for IndexVecIntoIter {} /// to adapt the internal `sample_floyd` implementation. /// /// Panics if `amount > length`. +#[track_caller] pub fn sample(rng: &mut R, length: usize, amount: usize) -> IndexVec where R: Rng + ?Sized { if amount > length { From 1b762b286721504e1fe4742edda2e09ddd1b4373 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Thu, 9 May 2024 09:50:08 +0300 Subject: [PATCH 383/443] Apply rustfmt and fix Clippy warnings (#1448) --- .github/workflows/benches.yml | 23 + .github/workflows/test.yml | 2 - .github/workflows/workspace.yml | 33 ++ Cargo.toml | 2 +- benches/benches/generators.rs | 3 - rand_chacha/src/chacha.rs | 26 +- rand_chacha/src/guts.rs | 12 +- rand_core/src/blanket_impls.rs | 3 +- rand_core/src/block.rs | 3 +- rand_core/src/impls.rs | 2 +- rand_core/src/lib.rs | 25 +- rand_distr/src/binomial.rs | 21 +- rand_distr/src/cauchy.rs | 35 +- rand_distr/src/dirichlet.rs | 3 +- rand_distr/src/exponential.rs | 24 +- rand_distr/src/gamma.rs | 56 ++- rand_distr/src/geometric.rs | 56 ++- rand_distr/src/hypergeometric.rs | 204 +++++--- rand_distr/src/inverse_gaussian.rs | 13 +- rand_distr/src/lib.rs | 9 +- rand_distr/src/normal.rs | 34 +- rand_distr/src/normal_inverse_gaussian.rs | 19 +- rand_distr/src/pareto.rs | 44 +- rand_distr/src/pert.rs | 16 +- rand_distr/src/poisson.rs | 27 +- rand_distr/src/skew_normal.rs | 16 +- rand_distr/src/triangular.rs | 29 +- rand_distr/src/unit_ball.rs | 2 +- rand_distr/src/unit_circle.rs | 2 +- rand_distr/src/unit_disc.rs | 2 +- rand_distr/src/unit_sphere.rs | 8 +- rand_distr/src/utils.rs | 6 +- rand_distr/src/weibull.rs | 47 +- rand_distr/src/weighted_alias.rs | 58 ++- rand_distr/src/weighted_tree.rs | 24 +- rand_distr/src/zipf.rs | 59 ++- rand_distr/tests/pdf.rs | 4 +- rand_distr/tests/sparkline.rs | 18 +- rand_distr/tests/value_stability.rs | 569 ++++++++++++++-------- rand_pcg/src/pcg128.rs | 4 +- rand_pcg/src/pcg128cm.rs | 3 +- rand_pcg/src/pcg64.rs | 3 +- rustfmt.toml | 32 -- src/distributions/bernoulli.rs | 12 +- src/distributions/distribution.rs | 3 +- src/distributions/float.rs | 95 ++-- src/distributions/integer.rs | 143 +++--- src/distributions/mod.rs | 2 +- src/distributions/other.rs | 122 ++--- src/distributions/slice.rs | 6 +- src/distributions/uniform.rs | 251 ++++++---- src/distributions/utils.rs | 17 +- src/distributions/weighted_index.rs | 72 +-- src/lib.rs | 11 +- src/prelude.rs | 6 +- src/rng.rs | 14 +- src/rngs/mock.rs | 6 +- src/rngs/mod.rs | 12 +- src/rngs/thread.rs | 1 - src/rngs/xoshiro128plusplus.rs | 3 +- src/rngs/xoshiro256plusplus.rs | 3 +- src/seq/coin_flipper.rs | 4 +- src/seq/index.rs | 99 ++-- src/seq/mod.rs | 30 +- 64 files changed, 1537 insertions(+), 956 deletions(-) create mode 100644 .github/workflows/benches.yml create mode 100644 .github/workflows/workspace.yml delete mode 100644 rustfmt.toml diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml new file mode 100644 index 00000000000..118a1765406 --- /dev/null +++ b/.github/workflows/benches.yml @@ -0,0 +1,23 @@ +name: Benches + +on: + pull_request: + paths: + - ".github/workflows/benches.yml" + - "benches/**" + +jobs: + benches: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: clippy, rustfmt + - name: Rustfmt + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy --all --all-targets -- -D warnings + - name: Build + run: RUSTFLAGS=-Dwarnings cargo build --all-targets diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 495571d2620..de886e48fb2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,8 +72,6 @@ jobs: if: ${{ matrix.variant == 'minimal_versions' }} run: | cargo generate-lockfile -Z minimal-versions - # Overrides for dependencies with incorrect requirements (may need periodic updating) - cargo update -p regex --precise 1.5.1 - name: Maybe nightly if: ${{ matrix.toolchain == 'nightly' }} run: | diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml new file mode 100644 index 00000000000..ef92b7f479c --- /dev/null +++ b/.github/workflows/workspace.yml @@ -0,0 +1,33 @@ +name: Workspace + +on: + pull_request: + paths-ignore: + - README.md + - "benches/**" + push: + branches: master + paths-ignore: + - README.md + - "benches/**" + +jobs: + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.78.0 + components: clippy + - run: cargo clippy --all --all-targets -- -D warnings + + rustfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - run: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index 8ffdff78190..a2ea562a28c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,12 +58,12 @@ unbiased = [] [workspace] members = [ - "benches", "rand_core", "rand_distr", "rand_chacha", "rand_pcg", ] +exclude = ["benches"] [dependencies] rand_core = { path = "rand_core", version = "=0.9.0-alpha.1", default-features = false } diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index 05090c24498..4019ec087ec 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -50,7 +50,6 @@ gen_bytes!(gen_bytes_chacha8, ChaCha8Rng::from_os_rng()); gen_bytes!(gen_bytes_chacha12, ChaCha12Rng::from_os_rng()); gen_bytes!(gen_bytes_chacha20, ChaCha20Rng::from_os_rng()); gen_bytes!(gen_bytes_std, StdRng::from_os_rng()); -#[cfg(feature = "small_rng")] gen_bytes!(gen_bytes_small, SmallRng::from_thread_rng()); gen_bytes!(gen_bytes_os, UnwrapErr(OsRng)); gen_bytes!(gen_bytes_thread, thread_rng()); @@ -81,7 +80,6 @@ gen_uint!(gen_u32_chacha8, u32, ChaCha8Rng::from_os_rng()); gen_uint!(gen_u32_chacha12, u32, ChaCha12Rng::from_os_rng()); gen_uint!(gen_u32_chacha20, u32, ChaCha20Rng::from_os_rng()); gen_uint!(gen_u32_std, u32, StdRng::from_os_rng()); -#[cfg(feature = "small_rng")] gen_uint!(gen_u32_small, u32, SmallRng::from_thread_rng()); gen_uint!(gen_u32_os, u32, UnwrapErr(OsRng)); gen_uint!(gen_u32_thread, u32, thread_rng()); @@ -95,7 +93,6 @@ gen_uint!(gen_u64_chacha8, u64, ChaCha8Rng::from_os_rng()); gen_uint!(gen_u64_chacha12, u64, ChaCha12Rng::from_os_rng()); gen_uint!(gen_u64_chacha20, u64, ChaCha20Rng::from_os_rng()); gen_uint!(gen_u64_std, u64, StdRng::from_os_rng()); -#[cfg(feature = "small_rng")] gen_uint!(gen_u64_small, u64, SmallRng::from_thread_rng()); gen_uint!(gen_u64_os, u64, UnwrapErr(OsRng)); gen_uint!(gen_u64_thread, u64, thread_rng()); diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 14be765a18b..3da47e45d76 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -8,8 +8,10 @@ //! The ChaCha random number generator. -#[cfg(not(feature = "std"))] use core; -#[cfg(feature = "std")] use std as core; +#[cfg(not(feature = "std"))] +use core; +#[cfg(feature = "std")] +use std as core; use self::core::fmt; use crate::guts::ChaCha; @@ -27,7 +29,8 @@ const BLOCK_WORDS: u8 = 16; #[repr(transparent)] pub struct Array64([T; 64]); impl Default for Array64 -where T: Default +where + T: Default, { #[rustfmt::skip] fn default() -> Self { @@ -54,7 +57,8 @@ impl AsMut<[T]> for Array64 { } } impl Clone for Array64 -where T: Copy + Default +where + T: Copy + Default, { fn clone(&self) -> Self { let mut new = Self::default(); @@ -275,20 +279,25 @@ macro_rules! chacha_impl { #[cfg(feature = "serde1")] impl Serialize for $ChaChaXRng { fn serialize(&self, s: S) -> Result - where S: Serializer { + where + S: Serializer, + { $abst::$ChaChaXRng::from(self).serialize(s) } } #[cfg(feature = "serde1")] impl<'de> Deserialize<'de> for $ChaChaXRng { fn deserialize(d: D) -> Result - where D: Deserializer<'de> { + where + D: Deserializer<'de>, + { $abst::$ChaChaXRng::deserialize(d).map(|x| Self::from(&x)) } } mod $abst { - #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; + #[cfg(feature = "serde1")] + use serde::{Deserialize, Serialize}; // The abstract state of a ChaCha stream, independent of implementation choices. The // comparison and serialization of this object is considered a semver-covered part of @@ -353,7 +362,8 @@ chacha_impl!( mod test { use rand_core::{RngCore, SeedableRng}; - #[cfg(feature = "serde1")] use super::{ChaCha12Rng, ChaCha20Rng, ChaCha8Rng}; + #[cfg(feature = "serde1")] + use super::{ChaCha12Rng, ChaCha20Rng, ChaCha8Rng}; type ChaChaRng = super::ChaCha20Rng; diff --git a/rand_chacha/src/guts.rs b/rand_chacha/src/guts.rs index 797ded6fa73..d077225c625 100644 --- a/rand_chacha/src/guts.rs +++ b/rand_chacha/src/guts.rs @@ -12,7 +12,9 @@ use ppv_lite86::{dispatch, dispatch_light128}; pub use ppv_lite86::Machine; -use ppv_lite86::{vec128_storage, ArithOps, BitOps32, LaneWords4, MultiLane, StoreBytes, Vec4, Vec4Ext, Vector}; +use ppv_lite86::{ + vec128_storage, ArithOps, BitOps32, LaneWords4, MultiLane, StoreBytes, Vec4, Vec4Ext, Vector, +}; pub(crate) const BLOCK: usize = 16; pub(crate) const BLOCK64: u64 = BLOCK as u64; @@ -140,14 +142,18 @@ fn add_pos(m: Mach, d: Mach::u32x4, i: u64) -> Mach::u32x4 { #[cfg(target_endian = "little")] fn d0123(m: Mach, d: vec128_storage) -> Mach::u32x4x4 { let d0: Mach::u64x2 = m.unpack(d); - let incr = Mach::u64x2x4::from_lanes([m.vec([0, 0]), m.vec([1, 0]), m.vec([2, 0]), m.vec([3, 0])]); + let incr = + Mach::u64x2x4::from_lanes([m.vec([0, 0]), m.vec([1, 0]), m.vec([2, 0]), m.vec([3, 0])]); m.unpack((Mach::u64x2x4::from_lanes([d0, d0, d0, d0]) + incr).into()) } #[allow(clippy::many_single_char_names)] #[inline(always)] fn refill_wide_impl( - m: Mach, state: &mut ChaCha, drounds: u32, out: &mut [u32; BUFSZ], + m: Mach, + state: &mut ChaCha, + drounds: u32, + out: &mut [u32; BUFSZ], ) { let k = m.vec([0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574]); let b = m.unpack(state.b); diff --git a/rand_core/src/blanket_impls.rs b/rand_core/src/blanket_impls.rs index cadd456ca5c..e3b54f894d1 100644 --- a/rand_core/src/blanket_impls.rs +++ b/rand_core/src/blanket_impls.rs @@ -1,4 +1,5 @@ -#[cfg(feature = "alloc")] use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::boxed::Box; use crate::{CryptoRng, RngCore, TryCryptoRng, TryRngCore}; diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index b5cc42bdbef..6872af432f5 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -56,7 +56,8 @@ use crate::impls::{fill_via_u32_chunks, fill_via_u64_chunks}; use crate::{CryptoRng, RngCore, SeedableRng, TryRngCore}; use core::fmt; -#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; /// A trait for RNGs which do not generate random numbers individually, but in /// blocks (typically `[u32; N]`). This technique is commonly used by diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index f9152fb2734..ff07d7834c1 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -199,7 +199,7 @@ macro_rules! impl_try_rng_from_rng_core { macro_rules! impl_try_crypto_rng_from_crypto_rng { ($t:ty) => { $crate::impl_try_rng_from_rng_core!($t); - + impl $crate::TryCryptoRng for $t {} /// Check at compile time that `$t` implements `CryptoRng` diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index d0c8af065c8..48c9c528dfc 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -32,11 +32,14 @@ #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] +#![allow(unexpected_cfgs)] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![no_std] -#[cfg(feature = "alloc")] extern crate alloc; -#[cfg(feature = "std")] extern crate std; +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; use core::fmt; @@ -44,11 +47,13 @@ mod blanket_impls; pub mod block; pub mod impls; pub mod le; -#[cfg(feature = "getrandom")] mod os; - -#[cfg(feature = "getrandom")] pub use getrandom; -#[cfg(feature = "getrandom")] pub use os::OsRng; +#[cfg(feature = "getrandom")] +mod os; +#[cfg(feature = "getrandom")] +pub use getrandom; +#[cfg(feature = "getrandom")] +pub use os::OsRng; /// The core of a random number generator. /// @@ -213,14 +218,18 @@ pub trait TryRngCore { /// Wrap RNG with the [`UnwrapErr`] wrapper. fn unwrap_err(self) -> UnwrapErr - where Self: Sized { + where + Self: Sized, + { UnwrapErr(self) } /// Convert an [`RngCore`] to a [`RngReadAdapter`]. #[cfg(feature = "std")] fn read_adapter(&mut self) -> RngReadAdapter<'_, Self> - where Self: Sized { + where + Self: Sized, + { RngReadAdapter { inner: self } } } diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 1e8e5e190a3..a9e6a708427 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -10,11 +10,11 @@ //! The binomial distribution. use crate::{Distribution, Uniform}; -use rand::Rng; -use core::fmt; use core::cmp::Ordering; +use core::fmt; #[allow(unused_imports)] use num_traits::Float; +use rand::Rng; /// The binomial distribution `Binomial(n, p)`. /// @@ -110,21 +110,21 @@ impl Distribution for Binomial { // Threshold for preferring the BINV algorithm. The paper suggests 10, // Ranlib uses 30, and GSL uses 14. const BINV_THRESHOLD: f64 = 10.; - + // Same value as in GSL. // It is possible for BINV to get stuck, so we break if x > BINV_MAX_X and try again. // It would be safer to set BINV_MAX_X to self.n, but it is extremely unlikely to be relevant. // When n*p < 10, so is n*p*q which is the variance, so a result > 110 would be 100 / sqrt(10) = 31 standard deviations away. - const BINV_MAX_X : u64 = 110; + const BINV_MAX_X: u64 = 110; if (self.n as f64) * p < BINV_THRESHOLD && self.n <= (i32::MAX as u64) { // Use the BINV algorithm. let s = p / q; let a = ((self.n + 1) as f64) * s; - + result = 'outer: loop { let mut r = q.powi(self.n as i32); - let mut u: f64 = rng.gen(); + let mut u: f64 = rng.random(); let mut x = 0; while u > r { @@ -136,7 +136,6 @@ impl Distribution for Binomial { r *= a / (x as f64) - s; } break x; - } } else { // Use the BTPE algorithm. @@ -238,7 +237,7 @@ impl Distribution for Binomial { break; } } - }, + } Ordering::Greater => { let mut i = y; loop { @@ -248,8 +247,8 @@ impl Distribution for Binomial { break; } } - }, - Ordering::Equal => {}, + } + Ordering::Equal => {} } if v > f { continue; @@ -366,7 +365,7 @@ mod test { fn binomial_distributions_can_be_compared() { assert_eq!(Binomial::new(1, 1.0), Binomial::new(1, 1.0)); } - + #[test] fn binomial_avoid_infinite_loop() { let dist = Binomial::new(16000000, 3.1444753148558566e-10).unwrap(); diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index fefaa737daf..5a445ff849e 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -9,10 +9,10 @@ //! The Cauchy distribution. -use num_traits::{Float, FloatConst}; use crate::{Distribution, Standard}; -use rand::Rng; use core::fmt; +use num_traits::{Float, FloatConst}; +use rand::Rng; /// The Cauchy distribution `Cauchy(median, scale)`. /// @@ -34,7 +34,9 @@ use core::fmt; #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Cauchy -where F: Float + FloatConst, Standard: Distribution +where + F: Float + FloatConst, + Standard: Distribution, { median: F, scale: F, @@ -60,7 +62,9 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl Cauchy -where F: Float + FloatConst, Standard: Distribution +where + F: Float + FloatConst, + Standard: Distribution, { /// Construct a new `Cauchy` with the given shape parameters /// `median` the peak location and `scale` the scale factor. @@ -73,7 +77,9 @@ where F: Float + FloatConst, Standard: Distribution } impl Distribution for Cauchy -where F: Float + FloatConst, Standard: Distribution +where + F: Float + FloatConst, + Standard: Distribution, { fn sample(&self, rng: &mut R) -> F { // sample from [0, 1) @@ -138,7 +144,9 @@ mod test { #[test] fn value_stability() { fn gen_samples(m: F, s: F, buf: &mut [F]) - where Standard: Distribution { + where + Standard: Distribution, + { let distr = Cauchy::new(m, s).unwrap(); let mut rng = crate::test::rng(353); for x in buf { @@ -148,12 +156,15 @@ mod test { let mut buf = [0.0; 4]; gen_samples(100f64, 10.0, &mut buf); - assert_eq!(&buf, &[ - 77.93369152808678, - 90.1606912098641, - 125.31516221323625, - 86.10217834773925 - ]); + assert_eq!( + &buf, + &[ + 77.93369152808678, + 90.1606912098641, + 125.31516221323625, + 86.10217834773925 + ] + ); // Unfortunately this test is not fully portable due to reliance on the // system's implementation of tanf (see doc on Cauchy struct). diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 413c00476ab..a7e11482a34 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -13,7 +13,8 @@ use crate::{Beta, Distribution, Exp1, Gamma, Open01, StandardNormal}; use core::fmt; use num_traits::{Float, NumCast}; use rand::Rng; -#[cfg(feature = "serde_with")] use serde_with::serde_as; +#[cfg(feature = "serde_with")] +use serde_with::serde_as; use alloc::{boxed::Box, vec, vec::Vec}; diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index e3d2a8d1cf6..1fa56a95b15 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -10,10 +10,10 @@ //! The exponential distribution. use crate::utils::ziggurat; -use num_traits::Float; use crate::{ziggurat_tables, Distribution}; -use rand::Rng; use core::fmt; +use num_traits::Float; +use rand::Rng; /// Samples floating-point numbers according to the exponential distribution, /// with rate parameter `λ = 1`. This is equivalent to `Exp::new(1.0)` or @@ -61,7 +61,7 @@ impl Distribution for Exp1 { } #[inline] fn zero_case(rng: &mut R, _u: f64) -> f64 { - ziggurat_tables::ZIG_EXP_R - rng.gen::().ln() + ziggurat_tables::ZIG_EXP_R - rng.random::().ln() } ziggurat( @@ -94,7 +94,9 @@ impl Distribution for Exp1 { #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Exp -where F: Float, Exp1: Distribution +where + F: Float, + Exp1: Distribution, { /// `lambda` stored as `1/lambda`, since this is what we scale by. lambda_inverse: F, @@ -120,16 +122,18 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl Exp -where F: Float, Exp1: Distribution +where + F: Float, + Exp1: Distribution, { /// Construct a new `Exp` with the given shape parameter /// `lambda`. - /// + /// /// # Remarks - /// + /// /// For custom types `N` implementing the [`Float`] trait, /// the case `lambda = 0` is handled as follows: each sample corresponds - /// to a sample from an `Exp1` multiplied by `1 / 0`. Primitive types + /// to a sample from an `Exp1` multiplied by `1 / 0`. Primitive types /// yield infinity, since `1 / 0 = infinity`. #[inline] pub fn new(lambda: F) -> Result, Error> { @@ -143,7 +147,9 @@ where F: Float, Exp1: Distribution } impl Distribution for Exp -where F: Float, Exp1: Distribution +where + F: Float, + Exp1: Distribution, { fn sample(&self, rng: &mut R) -> F { rng.sample(Exp1) * self.lambda_inverse diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 1a575bd6a9f..fbafd26824c 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -17,12 +17,12 @@ use self::ChiSquaredRepr::*; use self::GammaRepr::*; use crate::normal::StandardNormal; -use num_traits::Float; use crate::{Distribution, Exp, Exp1, Open01}; -use rand::Rng; use core::fmt; +use num_traits::Float; +use rand::Rng; #[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; /// The Gamma distribution `Gamma(shape, scale)` distribution. /// @@ -566,7 +566,9 @@ where F: Float, Open01: Distribution, { - a: F, b: F, switched_params: bool, + a: F, + b: F, + switched_params: bool, algorithm: BetaAlgorithm, } @@ -618,15 +620,19 @@ where if a > F::one() { // Algorithm BB let alpha = a + b; - let beta = ((alpha - F::from(2.).unwrap()) - / (F::from(2.).unwrap()*a*b - alpha)).sqrt(); + + let two = F::from(2.).unwrap(); + let beta_numer = alpha - two; + let beta_denom = two * a * b - alpha; + let beta = (beta_numer / beta_denom).sqrt(); + let gamma = a + F::one() / beta; Ok(Beta { - a, b, switched_params, - algorithm: BetaAlgorithm::BB(BB { - alpha, beta, gamma, - }) + a, + b, + switched_params, + algorithm: BetaAlgorithm::BB(BB { alpha, beta, gamma }), }) } else { // Algorithm BC @@ -637,16 +643,21 @@ where let beta = F::one() / b; let delta = F::one() + a - b; let kappa1 = delta - * (F::from(1. / 18. / 4.).unwrap() + F::from(3. / 18. / 4.).unwrap()*b) - / (a*beta - F::from(14. / 18.).unwrap()); + * (F::from(1. / 18. / 4.).unwrap() + F::from(3. / 18. / 4.).unwrap() * b) + / (a * beta - F::from(14. / 18.).unwrap()); let kappa2 = F::from(0.25).unwrap() - + (F::from(0.5).unwrap() + F::from(0.25).unwrap()/delta)*b; + + (F::from(0.5).unwrap() + F::from(0.25).unwrap() / delta) * b; Ok(Beta { - a, b, switched_params, + a, + b, + switched_params, algorithm: BetaAlgorithm::BC(BC { - alpha, beta, kappa1, kappa2, - }) + alpha, + beta, + kappa1, + kappa2, + }), }) } } @@ -667,12 +678,11 @@ where let u2 = rng.sample(Open01); let v = algo.beta * (u1 / (F::one() - u1)).ln(); w = self.a * v.exp(); - let z = u1*u1 * u2; + let z = u1 * u1 * u2; let r = algo.gamma * v - F::from(4.).unwrap().ln(); let s = self.a + r - w; // 2. - if s + F::one() + F::from(5.).unwrap().ln() - >= F::from(5.).unwrap() * z { + if s + F::one() + F::from(5.).unwrap().ln() >= F::from(5.).unwrap() * z { break; } // 3. @@ -685,7 +695,7 @@ where break; } } - }, + } BetaAlgorithm::BC(algo) => { loop { let z; @@ -716,11 +726,13 @@ where let v = algo.beta * (u1 / (F::one() - u1)).ln(); w = self.a * v.exp(); if !(algo.alpha * ((algo.alpha / (self.b + w)).ln() + v) - - F::from(4.).unwrap().ln() < z.ln()) { + - F::from(4.).unwrap().ln() + < z.ln()) + { break; }; } - }, + } }; // 5. for BB, 6. for BC if !self.switched_params { diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 5204013a5bd..0f14d4227d9 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -1,20 +1,20 @@ //! The geometric distribution. use crate::Distribution; -use rand::Rng; use core::fmt; #[allow(unused_imports)] use num_traits::Float; +use rand::Rng; /// The geometric distribution `Geometric(p)` bounded to `[0, u64::MAX]`. -/// +/// /// This is the probability distribution of the number of failures before the /// first success in a series of Bernoulli trials. It has the density function /// `f(k) = (1 - p)^k p` for `k >= 0`, where `p` is the probability of success /// on each trial. -/// +/// /// This is the discrete analogue of the [exponential distribution](crate::Exp). -/// +/// /// Note that [`StandardGeometric`](crate::StandardGeometric) is an optimised /// implementation for `p = 0.5`. /// @@ -29,11 +29,10 @@ use num_traits::Float; /// ``` #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Geometric -{ +pub struct Geometric { p: f64, pi: f64, - k: u64 + k: u64, } /// Error type returned from `Geometric::new`. @@ -46,7 +45,9 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - Error::InvalidProbability => "p is NaN or outside the interval [0, 1] in geometric distribution", + Error::InvalidProbability => { + "p is NaN or outside the interval [0, 1] in geometric distribution" + } }) } } @@ -80,21 +81,24 @@ impl Geometric { } } -impl Distribution for Geometric -{ +impl Distribution for Geometric { fn sample(&self, rng: &mut R) -> u64 { if self.p >= 2.0 / 3.0 { // use the trivial algorithm: let mut failures = 0; loop { - let u = rng.gen::(); - if u <= self.p { break; } + let u = rng.random::(); + if u <= self.p { + break; + } failures += 1; } return failures; } - - if self.p == 0.0 { return u64::MAX; } + + if self.p == 0.0 { + return u64::MAX; + } let Geometric { p, pi, k } = *self; @@ -108,7 +112,7 @@ impl Distribution for Geometric // Use the trivial algorithm to sample D from Geo(pi) = Geo(p) / 2^k: let d = { let mut failures = 0; - while rng.gen::() < pi { + while rng.random::() < pi { failures += 1; } failures @@ -116,18 +120,18 @@ impl Distribution for Geometric // Use rejection sampling for the remainder M from Geo(p) % 2^k: // choose M uniformly from [0, 2^k), but reject with probability (1 - p)^M - // NOTE: The paper suggests using bitwise sampling here, which is + // NOTE: The paper suggests using bitwise sampling here, which is // currently unsupported, but should improve performance by requiring // fewer iterations on average. ~ October 28, 2020 let m = loop { - let m = rng.gen::() & ((1 << k) - 1); + let m = rng.random::() & ((1 << k) - 1); let p_reject = if m <= i32::MAX as u64 { (1.0 - p).powi(m as i32) } else { (1.0 - p).powf(m as f64) }; - - let u = rng.gen::(); + + let u = rng.random::(); if u < p_reject { break m; } @@ -140,17 +144,17 @@ impl Distribution for Geometric /// Samples integers according to the geometric distribution with success /// probability `p = 0.5`. This is equivalent to `Geometeric::new(0.5)`, /// but faster. -/// +/// /// See [`Geometric`](crate::Geometric) for the general geometric distribution. -/// +/// /// Implemented via iterated /// [`Rng::gen::().leading_zeros()`](Rng::gen::().leading_zeros()). -/// +/// /// # Example /// ``` /// use rand::prelude::*; /// use rand_distr::StandardGeometric; -/// +/// /// let v = StandardGeometric.sample(&mut thread_rng()); /// println!("{} is from a Geometric(0.5) distribution", v); /// ``` @@ -162,9 +166,11 @@ impl Distribution for StandardGeometric { fn sample(&self, rng: &mut R) -> u64 { let mut result = 0; loop { - let x = rng.gen::().leading_zeros() as u64; + let x = rng.random::().leading_zeros() as u64; result += x; - if x < 64 { break; } + if x < 64 { + break; + } } result } diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 73a8e91c75e..84fde5437d8 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -1,17 +1,20 @@ //! The hypergeometric distribution. use crate::Distribution; -use rand::Rng; -use rand::distributions::uniform::Uniform; use core::fmt; #[allow(unused_imports)] use num_traits::Float; +use rand::distributions::uniform::Uniform; +use rand::Rng; #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] enum SamplingMethod { - InverseTransform{ initial_p: f64, initial_x: i64 }, - RejectionAcceptance{ + InverseTransform { + initial_p: f64, + initial_x: i64, + }, + RejectionAcceptance { m: f64, a: f64, lambda_l: f64, @@ -20,24 +23,24 @@ enum SamplingMethod { x_r: f64, p1: f64, p2: f64, - p3: f64 + p3: f64, }, } /// The hypergeometric distribution `Hypergeometric(N, K, n)`. -/// +/// /// This is the distribution of successes in samples of size `n` drawn without /// replacement from a population of size `N` containing `K` success states. /// It has the density function: /// `f(k) = binomial(K, k) * binomial(N-K, n-k) / binomial(N, n)`, /// where `binomial(a, b) = a! / (b! * (a - b)!)`. -/// +/// /// The [binomial distribution](crate::Binomial) is the analogous distribution /// for sampling with replacement. It is a good approximation when the population /// size is much larger than the sample size. -/// +/// /// # Example -/// +/// /// ``` /// use rand_distr::{Distribution, Hypergeometric}; /// @@ -70,9 +73,15 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - Error::PopulationTooLarge => "total_population_size is too large causing underflow in geometric distribution", - Error::ProbabilityTooLarge => "population_with_feature > total_population_size in geometric distribution", - Error::SampleSizeTooLarge => "sample_size > total_population_size in geometric distribution", + Error::PopulationTooLarge => { + "total_population_size is too large causing underflow in geometric distribution" + } + Error::ProbabilityTooLarge => { + "population_with_feature > total_population_size in geometric distribution" + } + Error::SampleSizeTooLarge => { + "sample_size > total_population_size in geometric distribution" + } }) } } @@ -97,20 +106,20 @@ fn fraction_of_products_of_factorials(numerator: (u64, u64), denominator: (u64, if i <= min_top { result *= i as f64; } - + if i <= min_bottom { result /= i as f64; } - + if i <= max_top { result *= i as f64; } - + if i <= max_bottom { result /= i as f64; } } - + result } @@ -126,7 +135,11 @@ impl Hypergeometric { /// `K = population_with_feature`, /// `n = sample_size`. #[allow(clippy::many_single_char_names)] // Same names as in the reference. - pub fn new(total_population_size: u64, population_with_feature: u64, sample_size: u64) -> Result { + pub fn new( + total_population_size: u64, + population_with_feature: u64, + sample_size: u64, + ) -> Result { if population_with_feature > total_population_size { return Err(Error::ProbabilityTooLarge); } @@ -151,7 +164,7 @@ impl Hypergeometric { }; // when sampling more than half the total population, take the smaller // group as sampled instead (we can then return n1-x instead). - // + // // Note: the boundary condition given in the paper is `sample_size < n / 2`; // we're deviating here, because when n is even, it doesn't matter whether // we switch here or not, but when n is odd `n/2 < n - n/2`, so switching @@ -167,7 +180,7 @@ impl Hypergeometric { // Algorithm H2PE has bounded runtime only if `M - max(0, k-n2) >= 10`, // where `M` is the mode of the distribution. // Use algorithm HIN for the remaining parameter space. - // + // // Voratas Kachitvichyanukul and Bruce W. Schmeiser. 1985. Computer // generation of hypergeometric random variates. // J. Statist. Comput. Simul. Vol.22 (August 1985), 127-145 @@ -176,21 +189,30 @@ impl Hypergeometric { let m = ((k + 1) as f64 * (n1 + 1) as f64 / (n + 2) as f64).floor(); let sampling_method = if m - f64::max(0.0, k as f64 - n2 as f64) < HIN_THRESHOLD { let (initial_p, initial_x) = if k < n2 { - (fraction_of_products_of_factorials((n2, n - k), (n, n2 - k)), 0) + ( + fraction_of_products_of_factorials((n2, n - k), (n, n2 - k)), + 0, + ) } else { - (fraction_of_products_of_factorials((n1, k), (n, k - n2)), (k - n2) as i64) + ( + fraction_of_products_of_factorials((n1, k), (n, k - n2)), + (k - n2) as i64, + ) }; if initial_p <= 0.0 || !initial_p.is_finite() { return Err(Error::PopulationTooLarge); } - SamplingMethod::InverseTransform { initial_p, initial_x } + SamplingMethod::InverseTransform { + initial_p, + initial_x, + } } else { - let a = ln_of_factorial(m) + - ln_of_factorial(n1 as f64 - m) + - ln_of_factorial(k as f64 - m) + - ln_of_factorial((n2 - k) as f64 + m); + let a = ln_of_factorial(m) + + ln_of_factorial(n1 as f64 - m) + + ln_of_factorial(k as f64 - m) + + ln_of_factorial((n2 - k) as f64 + m); let numerator = (n - k) as f64 * k as f64 * n1 as f64 * n2 as f64; let denominator = (n - 1) as f64 * n as f64 * n as f64; @@ -199,17 +221,19 @@ impl Hypergeometric { let x_l = m - d + 0.5; let x_r = m + d + 0.5; - let k_l = f64::exp(a - - ln_of_factorial(x_l) - - ln_of_factorial(n1 as f64 - x_l) - - ln_of_factorial(k as f64 - x_l) - - ln_of_factorial((n2 - k) as f64 + x_l)); - let k_r = f64::exp(a - - ln_of_factorial(x_r - 1.0) - - ln_of_factorial(n1 as f64 - x_r + 1.0) - - ln_of_factorial(k as f64 - x_r + 1.0) - - ln_of_factorial((n2 - k) as f64 + x_r - 1.0)); - + let k_l = f64::exp( + a - ln_of_factorial(x_l) + - ln_of_factorial(n1 as f64 - x_l) + - ln_of_factorial(k as f64 - x_l) + - ln_of_factorial((n2 - k) as f64 + x_l), + ); + let k_r = f64::exp( + a - ln_of_factorial(x_r - 1.0) + - ln_of_factorial(n1 as f64 - x_r + 1.0) + - ln_of_factorial(k as f64 - x_r + 1.0) + - ln_of_factorial((n2 - k) as f64 + x_r - 1.0), + ); + let numerator = x_l * ((n2 - k) as f64 + x_l); let denominator = (n1 as f64 - x_l + 1.0) * (k as f64 - x_l + 1.0); let lambda_l = -((numerator / denominator).ln()); @@ -225,11 +249,26 @@ impl Hypergeometric { let p3 = p2 + k_r / lambda_r; SamplingMethod::RejectionAcceptance { - m, a, lambda_l, lambda_r, x_l, x_r, p1, p2, p3 + m, + a, + lambda_l, + lambda_r, + x_l, + x_r, + p1, + p2, + p3, } }; - Ok(Hypergeometric { n1, n2, k, offset_x, sign_x, sampling_method }) + Ok(Hypergeometric { + n1, + n2, + k, + offset_x, + sign_x, + sampling_method, + }) } } @@ -238,25 +277,47 @@ impl Distribution for Hypergeometric { fn sample(&self, rng: &mut R) -> u64 { use SamplingMethod::*; - let Hypergeometric { n1, n2, k, sign_x, offset_x, sampling_method } = *self; + let Hypergeometric { + n1, + n2, + k, + sign_x, + offset_x, + sampling_method, + } = *self; let x = match sampling_method { - InverseTransform { initial_p: mut p, initial_x: mut x } => { - let mut u = rng.gen::(); - while u > p && x < k as i64 { // the paper erroneously uses `until n < p`, which doesn't make any sense + InverseTransform { + initial_p: mut p, + initial_x: mut x, + } => { + let mut u = rng.random::(); + + // the paper erroneously uses `until n < p`, which doesn't make any sense + while u > p && x < k as i64 { u -= p; p *= ((n1 as i64 - x) * (k as i64 - x)) as f64; p /= ((x + 1) * (n2 as i64 - k as i64 + 1 + x)) as f64; x += 1; } x - }, - RejectionAcceptance { m, a, lambda_l, lambda_r, x_l, x_r, p1, p2, p3 } => { + } + RejectionAcceptance { + m, + a, + lambda_l, + lambda_r, + x_l, + x_r, + p1, + p2, + p3, + } => { let distr_region_select = Uniform::new(0.0, p3).unwrap(); loop { let (y, v) = loop { let u = distr_region_select.sample(rng); - let v = rng.gen::(); // for the accept/reject decision - + let v = rng.random::(); // for the accept/reject decision + if u <= p1 { // Region 1, central bell let y = (x_l + u).floor(); @@ -277,7 +338,7 @@ impl Distribution for Hypergeometric { } } }; - + // Step 4: Acceptance/Rejection Comparison if m < 100.0 || y <= 50.0 { // Step 4.1: evaluate f(y) via recursive relationship @@ -293,8 +354,10 @@ impl Distribution for Hypergeometric { f /= (n1 - i) as f64 * (k - i) as f64; } } - - if v <= f { break y as i64; } + + if v <= f { + break y as i64; + } } else { // Step 4.2: Squeezing let y1 = y + 1.0; @@ -307,24 +370,24 @@ impl Distribution for Hypergeometric { let t = ym / yk; let e = -ym / nk; let g = yn * yk / (y1 * nk) - 1.0; - let dg = if g < 0.0 { - 1.0 + g - } else { - 1.0 - }; + let dg = if g < 0.0 { 1.0 + g } else { 1.0 }; let gu = g * (1.0 + g * (-0.5 + g / 3.0)); let gl = gu - g.powi(4) / (4.0 * dg); let xm = m + 0.5; let xn = n1 as f64 - m + 0.5; let xk = k as f64 - m + 0.5; let nm = n2 as f64 - k as f64 + xm; - let ub = xm * r * (1.0 + r * (-0.5 + r / 3.0)) + - xn * s * (1.0 + s * (-0.5 + s / 3.0)) + - xk * t * (1.0 + t * (-0.5 + t / 3.0)) + - nm * e * (1.0 + e * (-0.5 + e / 3.0)) + - y * gu - m * gl + 0.0034; + let ub = xm * r * (1.0 + r * (-0.5 + r / 3.0)) + + xn * s * (1.0 + s * (-0.5 + s / 3.0)) + + xk * t * (1.0 + t * (-0.5 + t / 3.0)) + + nm * e * (1.0 + e * (-0.5 + e / 3.0)) + + y * gu + - m * gl + + 0.0034; let av = v.ln(); - if av > ub { continue; } + if av > ub { + continue; + } let dr = if r < 0.0 { xm * r.powi(4) / (1.0 + r) } else { @@ -345,17 +408,17 @@ impl Distribution for Hypergeometric { } else { nm * e.powi(4) }; - - if av < ub - 0.25*(dr + ds + dt + de) + (y + m)*(gl - gu) - 0.0078 { + + if av < ub - 0.25 * (dr + ds + dt + de) + (y + m) * (gl - gu) - 0.0078 { break y as i64; } - + // Step 4.3: Final Acceptance/Rejection Test - let av_critical = a - - ln_of_factorial(y) - - ln_of_factorial(n1 as f64 - y) - - ln_of_factorial(k as f64 - y) - - ln_of_factorial((n2 - k) as f64 + y); + let av_critical = a + - ln_of_factorial(y) + - ln_of_factorial(n1 as f64 - y) + - ln_of_factorial(k as f64 - y) + - ln_of_factorial((n2 - k) as f64 + y); if v.ln() <= av_critical { break y as i64; } @@ -380,8 +443,7 @@ mod test { assert!(Hypergeometric::new(100, 10, 5).is_ok()); } - fn test_hypergeometric_mean_and_variance(n: u64, k: u64, s: u64, rng: &mut R) - { + fn test_hypergeometric_mean_and_variance(n: u64, k: u64, s: u64, rng: &mut R) { let distr = Hypergeometric::new(n, k, s).unwrap(); let expected_mean = s as f64 * k as f64 / n as f64; diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index ba845fd1505..6518ee9e958 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -1,7 +1,7 @@ use crate::{Distribution, Standard, StandardNormal}; +use core::fmt; use num_traits::Float; use rand::Rng; -use core::fmt; /// Error type returned from `InverseGaussian::new` #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -68,7 +68,9 @@ where { #[allow(clippy::many_single_char_names)] fn sample(&self, rng: &mut R) -> F - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let mu = self.mean; let l = self.shape; @@ -79,7 +81,7 @@ where let x = mu + mu_2l * (y - (F::from(4.).unwrap() * l * y + y * y).sqrt()); - let u: F = rng.gen(); + let u: F = rng.random(); if u <= mu / (mu + x) { return x; @@ -112,6 +114,9 @@ mod tests { #[test] fn inverse_gaussian_distributions_can_be_compared() { - assert_eq!(InverseGaussian::new(1.0, 2.0), InverseGaussian::new(1.0, 2.0)); + assert_eq!( + InverseGaussian::new(1.0, 2.0), + InverseGaussian::new(1.0, 2.0) + ); } } diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index dc155bb5d5d..ea57dd0f742 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -21,6 +21,7 @@ )] #![allow(clippy::neg_cmp_op_on_partial_ord)] // suggested fix too verbose #![no_std] +#![allow(unexpected_cfgs)] #![cfg_attr(doc_cfg, feature(doc_cfg))] //! Generating random samples from probability distributions. @@ -178,10 +179,14 @@ mod test { macro_rules! assert_almost_eq { ($a:expr, $b:expr, $prec:expr) => { let diff = ($a - $b).abs(); - assert!(diff <= $prec, + assert!( + diff <= $prec, "assertion failed: `abs(left - right) = {:.1e} < {:e}`, \ (left: `{}`, right: `{}`)", - diff, $prec, $a, $b + diff, + $prec, + $a, + $b ); }; } diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 635f26f1d43..8ef231b0f32 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -10,10 +10,10 @@ //! The normal and derived distributions. use crate::utils::ziggurat; -use num_traits::Float; use crate::{ziggurat_tables, Distribution, Open01}; -use rand::Rng; use core::fmt; +use num_traits::Float; +use rand::Rng; /// Samples floating-point numbers according to the normal distribution /// `N(0, 1)` (a.k.a. a standard normal, or Gaussian). This is equivalent to @@ -115,7 +115,9 @@ impl Distribution for StandardNormal { #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Normal -where F: Float, StandardNormal: Distribution +where + F: Float, + StandardNormal: Distribution, { mean: F, std_dev: F, @@ -144,7 +146,9 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl Normal -where F: Float, StandardNormal: Distribution +where + F: Float, + StandardNormal: Distribution, { /// Construct, from mean and standard deviation /// @@ -204,14 +208,15 @@ where F: Float, StandardNormal: Distribution } impl Distribution for Normal -where F: Float, StandardNormal: Distribution +where + F: Float, + StandardNormal: Distribution, { fn sample(&self, rng: &mut R) -> F { self.from_zscore(rng.sample(StandardNormal)) } } - /// The log-normal distribution `ln N(mean, std_dev**2)`. /// /// If `X` is log-normal distributed, then `ln(X)` is `N(mean, std_dev**2)` @@ -230,13 +235,17 @@ where F: Float, StandardNormal: Distribution #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct LogNormal -where F: Float, StandardNormal: Distribution +where + F: Float, + StandardNormal: Distribution, { norm: Normal, } impl LogNormal -where F: Float, StandardNormal: Distribution +where + F: Float, + StandardNormal: Distribution, { /// Construct, from (log-space) mean and standard deviation /// @@ -307,7 +316,9 @@ where F: Float, StandardNormal: Distribution } impl Distribution for LogNormal -where F: Float, StandardNormal: Distribution +where + F: Float, + StandardNormal: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> F { @@ -348,7 +359,10 @@ mod tests { #[test] fn test_log_normal_cv() { let lnorm = LogNormal::from_mean_cv(0.0, 0.0).unwrap(); - assert_eq!((lnorm.norm.mean, lnorm.norm.std_dev), (f64::NEG_INFINITY, 0.0)); + assert_eq!( + (lnorm.norm.mean, lnorm.norm.std_dev), + (f64::NEG_INFINITY, 0.0) + ); let lnorm = LogNormal::from_mean_cv(1.0, 0.0).unwrap(); assert_eq!((lnorm.norm.mean, lnorm.norm.std_dev), (0.0, 0.0)); diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index 7c5ad971710..b7988f40bb3 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -1,7 +1,7 @@ use crate::{Distribution, InverseGaussian, Standard, StandardNormal}; +use core::fmt; use num_traits::Float; use rand::Rng; -use core::fmt; /// Error type returned from `NormalInverseGaussian::new` #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -15,8 +15,12 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - Error::AlphaNegativeOrNull => "alpha <= 0 or is NaN in normal inverse Gaussian distribution", - Error::AbsoluteBetaNotLessThanAlpha => "|beta| >= alpha or is NaN in normal inverse Gaussian distribution", + Error::AlphaNegativeOrNull => { + "alpha <= 0 or is NaN in normal inverse Gaussian distribution" + } + Error::AbsoluteBetaNotLessThanAlpha => { + "|beta| >= alpha or is NaN in normal inverse Gaussian distribution" + } }) } } @@ -75,7 +79,9 @@ where Standard: Distribution, { fn sample(&self, rng: &mut R) -> F - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let inv_gauss = rng.sample(self.inverse_gaussian); self.beta * inv_gauss + inv_gauss.sqrt() * rng.sample(StandardNormal) @@ -105,6 +111,9 @@ mod tests { #[test] fn normal_inverse_gaussian_distributions_can_be_compared() { - assert_eq!(NormalInverseGaussian::new(1.0, 2.0), NormalInverseGaussian::new(1.0, 2.0)); + assert_eq!( + NormalInverseGaussian::new(1.0, 2.0), + NormalInverseGaussian::new(1.0, 2.0) + ); } } diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index 25c8e0537dd..952afec4bc5 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -8,10 +8,10 @@ //! The Pareto distribution. -use num_traits::Float; use crate::{Distribution, OpenClosed01}; -use rand::Rng; use core::fmt; +use num_traits::Float; +use rand::Rng; /// Samples floating-point numbers according to the Pareto distribution /// @@ -26,7 +26,9 @@ use core::fmt; #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Pareto -where F: Float, OpenClosed01: Distribution +where + F: Float, + OpenClosed01: Distribution, { scale: F, inv_neg_shape: F, @@ -55,7 +57,9 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl Pareto -where F: Float, OpenClosed01: Distribution +where + F: Float, + OpenClosed01: Distribution, { /// Construct a new Pareto distribution with given `scale` and `shape`. /// @@ -78,7 +82,9 @@ where F: Float, OpenClosed01: Distribution } impl Distribution for Pareto -where F: Float, OpenClosed01: Distribution +where + F: Float, + OpenClosed01: Distribution, { fn sample(&self, rng: &mut R) -> F { let u: F = OpenClosed01.sample(rng); @@ -112,7 +118,9 @@ mod tests { #[test] fn value_stability() { fn test_samples>( - distr: D, thresh: F, expected: &[F], + distr: D, + thresh: F, + expected: &[F], ) { let mut rng = crate::test::rng(213); for v in expected { @@ -121,15 +129,21 @@ mod tests { } } - test_samples(Pareto::new(1f32, 1.0).unwrap(), 1e-6, &[ - 1.0423688, 2.1235929, 4.132709, 1.4679428, - ]); - test_samples(Pareto::new(2.0, 0.5).unwrap(), 1e-14, &[ - 9.019295276219136, - 4.3097126018270595, - 6.837815045397157, - 105.8826669383772, - ]); + test_samples( + Pareto::new(1f32, 1.0).unwrap(), + 1e-6, + &[1.0423688, 2.1235929, 4.132709, 1.4679428], + ); + test_samples( + Pareto::new(2.0, 0.5).unwrap(), + 1e-14, + &[ + 9.019295276219136, + 4.3097126018270595, + 6.837815045397157, + 105.8826669383772, + ], + ); } #[test] diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index 9ed79bf28ff..48114d97be2 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -7,10 +7,10 @@ // except according to those terms. //! The PERT distribution. -use num_traits::Float; use crate::{Beta, Distribution, Exp1, Open01, StandardNormal}; -use rand::Rng; use core::fmt; +use num_traits::Float; +use rand::Rng; /// The PERT distribution. /// @@ -129,20 +129,12 @@ mod test { #[test] fn test_pert() { - for &(min, max, mode) in &[ - (-1., 1., 0.), - (1., 2., 1.), - (5., 25., 25.), - ] { + for &(min, max, mode) in &[(-1., 1., 0.), (1., 2., 1.), (5., 25., 25.)] { let _distr = Pert::new(min, max, mode).unwrap(); // TODO: test correctness } - for &(min, max, mode) in &[ - (-1., 1., 2.), - (-1., 1., -2.), - (2., 1., 1.), - ] { + for &(min, max, mode) in &[(-1., 1., 2.), (-1., 1., -2.), (2., 1., 1.)] { assert!(Pert::new(min, max, mode).is_err()); } } diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 50d74298356..5de3113de00 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -9,10 +9,10 @@ //! The Poisson distribution. -use num_traits::{Float, FloatConst}; use crate::{Cauchy, Distribution, Standard}; -use rand::Rng; use core::fmt; +use num_traits::{Float, FloatConst}; +use rand::Rng; /// The Poisson distribution `Poisson(lambda)`. /// @@ -31,7 +31,9 @@ use core::fmt; #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Poisson -where F: Float + FloatConst, Standard: Distribution +where + F: Float + FloatConst, + Standard: Distribution, { lambda: F, // precalculated values @@ -64,7 +66,9 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl Poisson -where F: Float + FloatConst, Standard: Distribution +where + F: Float + FloatConst, + Standard: Distribution, { /// Construct a new `Poisson` with the given shape parameter /// `lambda`. @@ -87,7 +91,9 @@ where F: Float + FloatConst, Standard: Distribution } impl Distribution for Poisson -where F: Float + FloatConst, Standard: Distribution +where + F: Float + FloatConst, + Standard: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> F { @@ -96,9 +102,9 @@ where F: Float + FloatConst, Standard: Distribution // for low expected values use the Knuth method if self.lambda < F::from(12.0).unwrap() { let mut result = F::one(); - let mut p = rng.gen::(); + let mut p = rng.random::(); while p > self.exp_lambda { - p = p*rng.gen::(); + p = p * rng.random::(); result = result + F::one(); } result - F::one() @@ -139,7 +145,7 @@ where F: Float + FloatConst, Standard: Distribution .exp(); // check with uniform random value - if below the threshold, we are within the target distribution - if rng.gen::() <= check { + if rng.random::() <= check { break; } } @@ -153,7 +159,8 @@ mod test { use super::*; fn test_poisson_avg_gen(lambda: F, tol: F) - where Standard: Distribution + where + Standard: Distribution, { let poisson = Poisson::new(lambda).unwrap(); let mut rng = crate::test::rng(123); @@ -173,7 +180,7 @@ mod test { test_poisson_avg_gen::(10.0, 0.1); test_poisson_avg_gen::(15.0, 0.1); - //Small lambda will use Knuth's method with exp_lambda == 1.0 + // Small lambda will use Knuth's method with exp_lambda == 1.0 test_poisson_avg_gen::(0.00000000000000005, 0.1); test_poisson_avg_gen::(0.00000000000000005, 0.1); } diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 3577147f863..ad7dd2b5635 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -150,9 +150,7 @@ where mod tests { use super::*; - fn test_samples>( - distr: D, zero: F, expected: &[F], - ) { + fn test_samples>(distr: D, zero: F, expected: &[F]) { let mut rng = crate::test::rng(213); let mut buf = [zero; 4]; for x in &mut buf { @@ -222,12 +220,7 @@ mod tests { test_samples( SkewNormal::new(f64::INFINITY, 1.0, 0.0).unwrap(), 0f64, - &[ - f64::INFINITY, - f64::INFINITY, - f64::INFINITY, - f64::INFINITY, - ], + &[f64::INFINITY, f64::INFINITY, f64::INFINITY, f64::INFINITY], ); test_samples( SkewNormal::new(f64::NEG_INFINITY, 1.0, 0.0).unwrap(), @@ -256,6 +249,9 @@ mod tests { #[test] fn skew_normal_distributions_can_be_compared() { - assert_eq!(SkewNormal::new(1.0, 2.0, 3.0), SkewNormal::new(1.0, 2.0, 3.0)); + assert_eq!( + SkewNormal::new(1.0, 2.0, 3.0), + SkewNormal::new(1.0, 2.0, 3.0) + ); } } diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index eef7d190133..083725f78c7 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -7,10 +7,10 @@ // except according to those terms. //! The triangular distribution. -use num_traits::Float; use crate::{Distribution, Standard}; -use rand::Rng; use core::fmt; +use num_traits::Float; +use rand::Rng; /// The triangular distribution. /// @@ -34,7 +34,9 @@ use core::fmt; #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Triangular -where F: Float, Standard: Distribution +where + F: Float, + Standard: Distribution, { min: F, max: F, @@ -66,7 +68,9 @@ impl fmt::Display for TriangularError { impl std::error::Error for TriangularError {} impl Triangular -where F: Float, Standard: Distribution +where + F: Float, + Standard: Distribution, { /// Set up the Triangular distribution with defined `min`, `max` and `mode`. #[inline] @@ -82,7 +86,9 @@ where F: Float, Standard: Distribution } impl Distribution for Triangular -where F: Float, Standard: Distribution +where + F: Float, + Standard: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> F { @@ -106,7 +112,7 @@ mod test { #[test] fn test_triangular() { let mut half_rng = mock::StepRng::new(0x8000_0000_0000_0000, 0); - assert_eq!(half_rng.gen::(), 0.5); + assert_eq!(half_rng.random::(), 0.5); for &(min, max, mode, median) in &[ (-1., 1., 0., 0.), (1., 2., 1., 2. - 0.5f64.sqrt()), @@ -122,17 +128,16 @@ mod test { assert_eq!(distr.sample(&mut half_rng), median); } - for &(min, max, mode) in &[ - (-1., 1., 2.), - (-1., 1., -2.), - (2., 1., 1.), - ] { + for &(min, max, mode) in &[(-1., 1., 2.), (-1., 1., -2.), (2., 1., 1.)] { assert!(Triangular::new(min, max, mode).is_err()); } } #[test] fn triangular_distributions_can_be_compared() { - assert_eq!(Triangular::new(1.0, 3.0, 2.0), Triangular::new(1.0, 3.0, 2.0)); + assert_eq!( + Triangular::new(1.0, 3.0, 2.0), + Triangular::new(1.0, 3.0, 2.0) + ); } } diff --git a/rand_distr/src/unit_ball.rs b/rand_distr/src/unit_ball.rs index 4d29612597f..84ba6909abf 100644 --- a/rand_distr/src/unit_ball.rs +++ b/rand_distr/src/unit_ball.rs @@ -6,8 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use num_traits::Float; use crate::{uniform::SampleUniform, Distribution, Uniform}; +use num_traits::Float; use rand::Rng; /// Samples uniformly from the unit ball (surface and interior) in three diff --git a/rand_distr/src/unit_circle.rs b/rand_distr/src/unit_circle.rs index f3dbe757aa9..8b67545300a 100644 --- a/rand_distr/src/unit_circle.rs +++ b/rand_distr/src/unit_circle.rs @@ -6,8 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use num_traits::Float; use crate::{uniform::SampleUniform, Distribution, Uniform}; +use num_traits::Float; use rand::Rng; /// Samples uniformly from the edge of the unit circle in two dimensions. diff --git a/rand_distr/src/unit_disc.rs b/rand_distr/src/unit_disc.rs index 5004217d5b7..bcf33c7924f 100644 --- a/rand_distr/src/unit_disc.rs +++ b/rand_distr/src/unit_disc.rs @@ -6,8 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use num_traits::Float; use crate::{uniform::SampleUniform, Distribution, Uniform}; +use num_traits::Float; use rand::Rng; /// Samples uniformly from the unit disc in two dimensions. diff --git a/rand_distr/src/unit_sphere.rs b/rand_distr/src/unit_sphere.rs index 632275e3327..5e7f8fe7712 100644 --- a/rand_distr/src/unit_sphere.rs +++ b/rand_distr/src/unit_sphere.rs @@ -6,8 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use num_traits::Float; use crate::{uniform::SampleUniform, Distribution, Uniform}; +use num_traits::Float; use rand::Rng; /// Samples uniformly from the surface of the unit sphere in three dimensions. @@ -42,7 +42,11 @@ impl Distribution<[F; 3]> for UnitSphere { continue; } let factor = F::from(2.).unwrap() * (F::one() - sum).sqrt(); - return [x1 * factor, x2 * factor, F::from(1.).unwrap() - F::from(2.).unwrap() * sum]; + return [ + x1 * factor, + x2 * factor, + F::from(1.).unwrap() - F::from(2.).unwrap() * sum, + ]; } } } diff --git a/rand_distr/src/utils.rs b/rand_distr/src/utils.rs index 052bfc49991..fb49ab85762 100644 --- a/rand_distr/src/utils.rs +++ b/rand_distr/src/utils.rs @@ -9,9 +9,9 @@ //! Math helper functions use crate::ziggurat_tables; +use num_traits::Float; use rand::distributions::hidden_export::IntoFloat; use rand::Rng; -use num_traits::Float; /// Calculates ln(gamma(x)) (natural logarithm of the gamma /// function) using the Lanczos approximation. @@ -77,7 +77,7 @@ pub(crate) fn ziggurat( x_tab: ziggurat_tables::ZigTable, f_tab: ziggurat_tables::ZigTable, mut pdf: P, - mut zero_case: Z + mut zero_case: Z, ) -> f64 where P: FnMut(f64) -> f64, @@ -114,7 +114,7 @@ where return zero_case(rng, u); } // algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1 - if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.gen::() < pdf(x) { + if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.random::() < pdf(x) { return x; } } diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index 2ab74edde2c..2fba2a87079 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -8,10 +8,10 @@ //! The Weibull distribution. -use num_traits::Float; use crate::{Distribution, OpenClosed01}; -use rand::Rng; use core::fmt; +use num_traits::Float; +use rand::Rng; /// Samples floating-point numbers according to the Weibull distribution /// @@ -26,7 +26,9 @@ use core::fmt; #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Weibull -where F: Float, OpenClosed01: Distribution +where + F: Float, + OpenClosed01: Distribution, { inv_shape: F, scale: F, @@ -55,7 +57,9 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl Weibull -where F: Float, OpenClosed01: Distribution +where + F: Float, + OpenClosed01: Distribution, { /// Construct a new `Weibull` distribution with given `scale` and `shape`. pub fn new(scale: F, shape: F) -> Result, Error> { @@ -73,7 +77,9 @@ where F: Float, OpenClosed01: Distribution } impl Distribution for Weibull -where F: Float, OpenClosed01: Distribution +where + F: Float, + OpenClosed01: Distribution, { fn sample(&self, rng: &mut R) -> F { let x: F = rng.sample(OpenClosed01); @@ -106,7 +112,9 @@ mod tests { #[test] fn value_stability() { fn test_samples>( - distr: D, zero: F, expected: &[F], + distr: D, + zero: F, + expected: &[F], ) { let mut rng = crate::test::rng(213); let mut buf = [zero; 4]; @@ -116,18 +124,21 @@ mod tests { assert_eq!(buf, expected); } - test_samples(Weibull::new(1.0, 1.0).unwrap(), 0f32, &[ - 0.041495778, - 0.7531094, - 1.4189332, - 0.38386202, - ]); - test_samples(Weibull::new(2.0, 0.5).unwrap(), 0f64, &[ - 1.1343478702739669, - 0.29470010050655226, - 0.7556151370284702, - 7.877212340241561, - ]); + test_samples( + Weibull::new(1.0, 1.0).unwrap(), + 0f32, + &[0.041495778, 0.7531094, 1.4189332, 0.38386202], + ); + test_samples( + Weibull::new(2.0, 0.5).unwrap(), + 0f64, + &[ + 1.1343478702739669, + 0.29470010050655226, + 0.7556151370284702, + 7.877212340241561, + ], + ); } #[test] diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 0c07ead0df9..beb31f0733b 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -11,13 +11,13 @@ use super::WeightError; use crate::{uniform::SampleUniform, Distribution, Uniform}; +use alloc::{boxed::Box, vec, vec::Vec}; use core::fmt; use core::iter::Sum; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use rand::Rng; -use alloc::{boxed::Box, vec, vec::Vec}; #[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; /// A distribution using weighted sampling to pick a discretely selected item. /// @@ -67,8 +67,14 @@ use serde::{Serialize, Deserialize}; /// [`Uniform::sample`]: Distribution::sample #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde1", serde(bound(serialize = "W: Serialize, W::Sampler: Serialize")))] -#[cfg_attr(feature = "serde1", serde(bound(deserialize = "W: Deserialize<'de>, W::Sampler: Deserialize<'de>")))] +#[cfg_attr( + feature = "serde1", + serde(bound(serialize = "W: Serialize, W::Sampler: Serialize")) +)] +#[cfg_attr( + feature = "serde1", + serde(bound(deserialize = "W: Deserialize<'de>, W::Sampler: Deserialize<'de>")) +)] pub struct WeightedAliasIndex { aliases: Box<[u32]>, no_alias_odds: Box<[W]>, @@ -257,7 +263,8 @@ where } impl Clone for WeightedAliasIndex -where Uniform: Clone +where + Uniform: Clone, { fn clone(&self) -> Self { Self { @@ -308,7 +315,7 @@ pub trait AliasableWeight: macro_rules! impl_weight_for_float { ($T: ident) => { impl AliasableWeight for $T { - const MAX: Self = ::core::$T::MAX; + const MAX: Self = $T::MAX; const ZERO: Self = 0.0; fn try_from_u32_lossy(n: u32) -> Option { @@ -337,7 +344,7 @@ fn pairwise_sum(values: &[T]) -> T { macro_rules! impl_weight_for_int { ($T: ident) => { impl AliasableWeight for $T { - const MAX: Self = ::core::$T::MAX; + const MAX: Self = $T::MAX; const ZERO: Self = 0; fn try_from_u32_lossy(n: u32) -> Option { @@ -444,7 +451,9 @@ mod test { } fn test_weighted_index f64>(w_to_f64: F) - where WeightedAliasIndex: fmt::Debug { + where + WeightedAliasIndex: fmt::Debug, + { const NUM_WEIGHTS: u32 = 10; const ZERO_WEIGHT_INDEX: u32 = 3; const NUM_SAMPLES: u32 = 15000; @@ -455,7 +464,8 @@ mod test { let random_weight_distribution = Uniform::new_inclusive( W::ZERO, W::MAX / W::try_from_u32_lossy(NUM_WEIGHTS).unwrap(), - ).unwrap(); + ) + .unwrap(); for _ in 0..NUM_WEIGHTS { weights.push(rng.sample(&random_weight_distribution)); } @@ -497,7 +507,11 @@ mod test { #[test] fn value_stability() { - fn test_samples(weights: Vec, buf: &mut [usize], expected: &[usize]) { + fn test_samples( + weights: Vec, + buf: &mut [usize], + expected: &[usize], + ) { assert_eq!(buf.len(), expected.len()); let distr = WeightedAliasIndex::new(weights).unwrap(); let mut rng = crate::test::rng(0x9c9fa0b0580a7031); @@ -508,14 +522,20 @@ mod test { } let mut buf = [0; 10]; - test_samples(vec![1i32, 1, 1, 1, 1, 1, 1, 1, 1], &mut buf, &[ - 6, 5, 7, 5, 8, 7, 6, 2, 3, 7, - ]); - test_samples(vec![0.7f32, 0.1, 0.1, 0.1], &mut buf, &[ - 2, 0, 0, 0, 0, 0, 0, 0, 1, 3, - ]); - test_samples(vec![1.0f64, 0.999, 0.998, 0.997], &mut buf, &[ - 2, 1, 2, 3, 2, 1, 3, 2, 1, 1, - ]); + test_samples( + vec![1i32, 1, 1, 1, 1, 1, 1, 1, 1], + &mut buf, + &[6, 5, 7, 5, 8, 7, 6, 2, 3, 7], + ); + test_samples( + vec![0.7f32, 0.1, 0.1, 0.1], + &mut buf, + &[2, 0, 0, 0, 0, 0, 0, 0, 1, 3], + ); + test_samples( + vec![1.0f64, 0.999, 0.998, 0.997], + &mut buf, + &[2, 1, 2, 3, 2, 1, 3, 2, 1, 1], + ); } } diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index c292578bf4e..67ce48bad80 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -303,6 +303,7 @@ mod test { #[test] fn test_no_item_error() { let mut rng = crate::test::rng(0x9c9fa0b0580a7031); + #[allow(clippy::needless_borrows_for_generic_args)] let tree = WeightedTreeIndex::::new(&[]).unwrap(); assert_eq!( tree.try_sample(&mut rng).unwrap_err(), @@ -313,10 +314,10 @@ mod test { #[test] fn test_overflow_error() { assert_eq!( - WeightedTreeIndex::new(&[i32::MAX, 2]), + WeightedTreeIndex::new([i32::MAX, 2]), Err(WeightError::Overflow) ); - let mut tree = WeightedTreeIndex::new(&[i32::MAX - 2, 1]).unwrap(); + let mut tree = WeightedTreeIndex::new([i32::MAX - 2, 1]).unwrap(); assert_eq!(tree.push(3), Err(WeightError::Overflow)); assert_eq!(tree.update(1, 4), Err(WeightError::Overflow)); tree.update(1, 2).unwrap(); @@ -324,7 +325,7 @@ mod test { #[test] fn test_all_weights_zero_error() { - let tree = WeightedTreeIndex::::new(&[0.0, 0.0]).unwrap(); + let tree = WeightedTreeIndex::::new([0.0, 0.0]).unwrap(); let mut rng = crate::test::rng(0x9c9fa0b0580a7031); assert_eq!( tree.try_sample(&mut rng).unwrap_err(), @@ -335,37 +336,36 @@ mod test { #[test] fn test_invalid_weight_error() { assert_eq!( - WeightedTreeIndex::::new(&[1, -1]).unwrap_err(), + WeightedTreeIndex::::new([1, -1]).unwrap_err(), WeightError::InvalidWeight ); + #[allow(clippy::needless_borrows_for_generic_args)] let mut tree = WeightedTreeIndex::::new(&[]).unwrap(); assert_eq!(tree.push(-1).unwrap_err(), WeightError::InvalidWeight); tree.push(1).unwrap(); - assert_eq!( - tree.update(0, -1).unwrap_err(), - WeightError::InvalidWeight - ); + assert_eq!(tree.update(0, -1).unwrap_err(), WeightError::InvalidWeight); } #[test] fn test_tree_modifications() { - let mut tree = WeightedTreeIndex::new(&[9, 1, 2]).unwrap(); + let mut tree = WeightedTreeIndex::new([9, 1, 2]).unwrap(); tree.push(3).unwrap(); tree.push(5).unwrap(); tree.update(0, 0).unwrap(); assert_eq!(tree.pop(), Some(5)); - let expected = WeightedTreeIndex::new(&[0, 1, 2, 3]).unwrap(); + let expected = WeightedTreeIndex::new([0, 1, 2, 3]).unwrap(); assert_eq!(tree, expected); } #[test] + #[allow(clippy::needless_range_loop)] fn test_sample_counts_match_probabilities() { let start = 1; let end = 3; let samples = 20; let mut rng = crate::test::rng(0x9c9fa0b0580a7031); - let weights: Vec<_> = (0..end).map(|_| rng.gen()).collect(); - let mut tree = WeightedTreeIndex::new(&weights).unwrap(); + let weights: Vec = (0..end).map(|_| rng.random()).collect(); + let mut tree = WeightedTreeIndex::new(weights).unwrap(); let mut total_weight = 0.0; let mut weights = alloc::vec![0.0; end]; for i in 0..end { diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index d0813ef9066..7b207a46a78 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -8,10 +8,10 @@ //! The Zeta and related distributions. -use num_traits::Float; use crate::{Distribution, Standard}; -use rand::{Rng, distributions::OpenClosed01}; use core::fmt; +use num_traits::Float; +use rand::{distributions::OpenClosed01, Rng}; /// Samples integers according to the [zeta distribution]. /// @@ -48,7 +48,10 @@ use core::fmt; /// [Non-Uniform Random Variate Generation]: https://doi.org/10.1007/978-1-4613-8643-8 #[derive(Clone, Copy, Debug, PartialEq)] pub struct Zeta -where F: Float, Standard: Distribution, OpenClosed01: Distribution +where + F: Float, + Standard: Distribution, + OpenClosed01: Distribution, { a_minus_1: F, b: F, @@ -74,7 +77,10 @@ impl fmt::Display for ZetaError { impl std::error::Error for ZetaError {} impl Zeta -where F: Float, Standard: Distribution, OpenClosed01: Distribution +where + F: Float, + Standard: Distribution, + OpenClosed01: Distribution, { /// Construct a new `Zeta` distribution with given `a` parameter. #[inline] @@ -92,7 +98,10 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution } impl Distribution for Zeta -where F: Float, Standard: Distribution, OpenClosed01: Distribution +where + F: Float, + Standard: Distribution, + OpenClosed01: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> F { @@ -144,7 +153,10 @@ where F: Float, Standard: Distribution, OpenClosed01: Distribution /// [1]: https://jasoncrease.medium.com/rejection-sampling-the-zipf-distribution-6b359792cffa #[derive(Clone, Copy, Debug, PartialEq)] pub struct Zipf -where F: Float, Standard: Distribution { +where + F: Float, + Standard: Distribution, +{ s: F, t: F, q: F, @@ -173,7 +185,10 @@ impl fmt::Display for ZipfError { impl std::error::Error for ZipfError {} impl Zipf -where F: Float, Standard: Distribution { +where + F: Float, + Standard: Distribution, +{ /// Construct a new `Zipf` distribution for a set with `n` elements and a /// frequency rank exponent `s`. /// @@ -186,7 +201,7 @@ where F: Float, Standard: Distribution { if n < 1 { return Err(ZipfError::NTooSmall); } - let n = F::from(n).unwrap(); // This does not fail. + let n = F::from(n).unwrap(); // This does not fail. let q = if s != F::one() { // Make sure to calculate the division only once. F::one() / (F::one() - s) @@ -200,9 +215,7 @@ where F: Float, Standard: Distribution { F::one() + n.ln() }; debug_assert!(t > F::zero()); - Ok(Zipf { - s, t, q - }) + Ok(Zipf { s, t, q }) } /// Inverse cumulative density function @@ -221,7 +234,9 @@ where F: Float, Standard: Distribution { } impl Distribution for Zipf -where F: Float, Standard: Distribution +where + F: Float, + Standard: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> F { @@ -246,9 +261,7 @@ where F: Float, Standard: Distribution mod tests { use super::*; - fn test_samples>( - distr: D, zero: F, expected: &[F], - ) { + fn test_samples>(distr: D, zero: F, expected: &[F]) { let mut rng = crate::test::rng(213); let mut buf = [zero; 4]; for x in &mut buf { @@ -293,12 +306,8 @@ mod tests { #[test] fn zeta_value_stability() { - test_samples(Zeta::new(1.5).unwrap(), 0f32, &[ - 1.0, 2.0, 1.0, 1.0, - ]); - test_samples(Zeta::new(2.0).unwrap(), 0f64, &[ - 2.0, 1.0, 1.0, 1.0, - ]); + test_samples(Zeta::new(1.5).unwrap(), 0f32, &[1.0, 2.0, 1.0, 1.0]); + test_samples(Zeta::new(2.0).unwrap(), 0f64, &[2.0, 1.0, 1.0, 1.0]); } #[test] @@ -363,12 +372,8 @@ mod tests { #[test] fn zipf_value_stability() { - test_samples(Zipf::new(10, 0.5).unwrap(), 0f32, &[ - 10.0, 2.0, 6.0, 7.0 - ]); - test_samples(Zipf::new(10, 2.0).unwrap(), 0f64, &[ - 1.0, 2.0, 3.0, 2.0 - ]); + test_samples(Zipf::new(10, 0.5).unwrap(), 0f32, &[10.0, 2.0, 6.0, 7.0]); + test_samples(Zipf::new(10, 2.0).unwrap(), 0f64, &[1.0, 2.0, 3.0, 2.0]); } #[test] diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index be5ee0e2595..47b00ef7391 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -57,7 +57,7 @@ fn normal() { let mut diff = [0.; HIST_LEN]; for (i, n) in hist.normalized_bins().enumerate() { - let bin = (n as f64) / (N_SAMPLES as f64); + let bin = n / (N_SAMPLES as f64); diff[i] = (bin - expected[i]).abs(); } @@ -140,7 +140,7 @@ fn skew_normal() { let mut diff = [0.; HIST_LEN]; for (i, n) in hist.normalized_bins().enumerate() { - let bin = (n as f64) / (N_SAMPLES as f64); + let bin = n / (N_SAMPLES as f64); diff[i] = (bin - expected[i]).abs(); } diff --git a/rand_distr/tests/sparkline.rs b/rand_distr/tests/sparkline.rs index ee6c9788d9c..ec0ee98de98 100644 --- a/rand_distr/tests/sparkline.rs +++ b/rand_distr/tests/sparkline.rs @@ -16,7 +16,7 @@ pub fn render_u64(data: &[u64], buffer: &mut String) { match data.len() { 0 => { return; - }, + } 1 => { if data[0] == 0 { buffer.push(TICKS[0]); @@ -24,8 +24,8 @@ pub fn render_u64(data: &[u64], buffer: &mut String) { buffer.push(TICKS[N - 1]); } return; - }, - _ => {}, + } + _ => {} } let max = data.iter().max().unwrap(); let min = data.iter().min().unwrap(); @@ -56,7 +56,7 @@ pub fn render_f64(data: &[f64], buffer: &mut String) { match data.len() { 0 => { return; - }, + } 1 => { if data[0] == 0. { buffer.push(TICKS[0]); @@ -64,16 +64,14 @@ pub fn render_f64(data: &[f64], buffer: &mut String) { buffer.push(TICKS[N - 1]); } return; - }, - _ => {}, + } + _ => {} } for x in data { assert!(x.is_finite(), "can only render finite values"); } - let max = data.iter().fold( - f64::NEG_INFINITY, |a, &b| a.max(b)); - let min = data.iter().fold( - f64::INFINITY, |a, &b| a.min(b)); + let max = data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)); + let min = data.iter().fold(f64::INFINITY, |a, &b| a.min(b)); let scale = ((N - 1) as f64) / (max - min); for x in data { let tick = ((x - min) * scale) as usize; diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index 7006dd0e816..31bfce52e3e 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -53,9 +53,7 @@ impl ApproxEq for [T; 3] { } } -fn test_samples>( - seed: u64, distr: D, expected: &[F], -) { +fn test_samples>(seed: u64, distr: D, expected: &[F]) { let mut rng = get_rng(seed); for val in expected { let x = rng.sample(&distr); @@ -68,16 +66,28 @@ fn binomial_stability() { // We have multiple code paths: np < 10, p > 0.5 test_samples(353, Binomial::new(2, 0.7).unwrap(), &[1, 1, 2, 1]); test_samples(353, Binomial::new(20, 0.3).unwrap(), &[7, 7, 5, 7]); - test_samples(353, Binomial::new(2000, 0.6).unwrap(), &[1194, 1208, 1192, 1210]); + test_samples( + 353, + Binomial::new(2000, 0.6).unwrap(), + &[1194, 1208, 1192, 1210], + ); } #[test] fn geometric_stability() { test_samples(464, StandardGeometric, &[3, 0, 1, 0, 0, 3, 2, 1, 2, 0]); - + test_samples(464, Geometric::new(0.5).unwrap(), &[2, 1, 1, 0, 0, 1, 0, 1]); - test_samples(464, Geometric::new(0.05).unwrap(), &[24, 51, 81, 67, 27, 11, 7, 6]); - test_samples(464, Geometric::new(0.95).unwrap(), &[0, 0, 0, 0, 1, 0, 0, 0]); + test_samples( + 464, + Geometric::new(0.05).unwrap(), + &[24, 51, 81, 67, 27, 11, 7, 6], + ); + test_samples( + 464, + Geometric::new(0.95).unwrap(), + &[0, 0, 0, 0, 1, 0, 0, 0], + ); // expect non-random behaviour for series of pre-determined trials test_samples(464, Geometric::new(0.0).unwrap(), &[u64::MAX; 100][..]); @@ -87,260 +97,404 @@ fn geometric_stability() { #[test] fn hypergeometric_stability() { // We have multiple code paths based on the distribution's mode and sample_size - test_samples(7221, Hypergeometric::new(99, 33, 8).unwrap(), &[4, 3, 2, 2, 3, 2, 3, 1]); // Algorithm HIN - test_samples(7221, Hypergeometric::new(100, 50, 50).unwrap(), &[23, 27, 26, 27, 22, 24, 31, 22]); // Algorithm H2PE + test_samples( + 7221, + Hypergeometric::new(99, 33, 8).unwrap(), + &[4, 3, 2, 2, 3, 2, 3, 1], + ); // Algorithm HIN + test_samples( + 7221, + Hypergeometric::new(100, 50, 50).unwrap(), + &[23, 27, 26, 27, 22, 24, 31, 22], + ); // Algorithm H2PE } #[test] fn unit_ball_stability() { - test_samples(2, UnitBall, &[ - [0.018035709265959987f64, -0.4348771383120438, -0.07982762085055706], - [0.10588569388223945, -0.4734350111375454, -0.7392104908825501], - [0.11060237642041049, -0.16065642822852677, -0.8444043930440075] - ]); + test_samples( + 2, + UnitBall, + &[ + [ + 0.018035709265959987f64, + -0.4348771383120438, + -0.07982762085055706, + ], + [ + 0.10588569388223945, + -0.4734350111375454, + -0.7392104908825501, + ], + [ + 0.11060237642041049, + -0.16065642822852677, + -0.8444043930440075, + ], + ], + ); } #[test] fn unit_circle_stability() { - test_samples(2, UnitCircle, &[ - [-0.9965658683520504f64, -0.08280380447614634], - [-0.9790853270389644, -0.20345004884984505], - [-0.8449189758898707, 0.5348943112253227], - ]); + test_samples( + 2, + UnitCircle, + &[ + [-0.9965658683520504f64, -0.08280380447614634], + [-0.9790853270389644, -0.20345004884984505], + [-0.8449189758898707, 0.5348943112253227], + ], + ); } #[test] fn unit_sphere_stability() { - test_samples(2, UnitSphere, &[ - [0.03247542860231647f64, -0.7830477442152738, 0.6211131755296027], - [-0.09978440840914075, 0.9706650829833128, -0.21875184231323952], - [0.2735582468624679, 0.9435374242279655, -0.1868234852870203], - ]); + test_samples( + 2, + UnitSphere, + &[ + [ + 0.03247542860231647f64, + -0.7830477442152738, + 0.6211131755296027, + ], + [ + -0.09978440840914075, + 0.9706650829833128, + -0.21875184231323952, + ], + [0.2735582468624679, 0.9435374242279655, -0.1868234852870203], + ], + ); } #[test] fn unit_disc_stability() { - test_samples(2, UnitDisc, &[ - [0.018035709265959987f64, -0.4348771383120438], - [-0.07982762085055706, 0.7765329819820659], - [0.21450745997299503, 0.7398636984333291], - ]); + test_samples( + 2, + UnitDisc, + &[ + [0.018035709265959987f64, -0.4348771383120438], + [-0.07982762085055706, 0.7765329819820659], + [0.21450745997299503, 0.7398636984333291], + ], + ); } #[test] fn pareto_stability() { - test_samples(213, Pareto::new(1.0, 1.0).unwrap(), &[ - 1.0423688f32, 2.1235929, 4.132709, 1.4679428, - ]); - test_samples(213, Pareto::new(2.0, 0.5).unwrap(), &[ - 9.019295276219136f64, - 4.3097126018270595, - 6.837815045397157, - 105.8826669383772, - ]); + test_samples( + 213, + Pareto::new(1.0, 1.0).unwrap(), + &[1.0423688f32, 2.1235929, 4.132709, 1.4679428], + ); + test_samples( + 213, + Pareto::new(2.0, 0.5).unwrap(), + &[ + 9.019295276219136f64, + 4.3097126018270595, + 6.837815045397157, + 105.8826669383772, + ], + ); } #[test] fn poisson_stability() { test_samples(223, Poisson::new(7.0).unwrap(), &[5.0f32, 11.0, 6.0, 5.0]); test_samples(223, Poisson::new(7.0).unwrap(), &[9.0f64, 5.0, 7.0, 6.0]); - test_samples(223, Poisson::new(27.0).unwrap(), &[28.0f32, 32.0, 36.0, 36.0]); + test_samples( + 223, + Poisson::new(27.0).unwrap(), + &[28.0f32, 32.0, 36.0, 36.0], + ); } - #[test] fn triangular_stability() { - test_samples(860, Triangular::new(2., 10., 3.).unwrap(), &[ - 5.74373257511361f64, - 7.890059162791258f64, - 4.7256280652553455f64, - 2.9474808121184077f64, - 3.058301946314053f64, - ]); + test_samples( + 860, + Triangular::new(2., 10., 3.).unwrap(), + &[ + 5.74373257511361f64, + 7.890059162791258f64, + 4.7256280652553455f64, + 2.9474808121184077f64, + 3.058301946314053f64, + ], + ); } - #[test] fn normal_inverse_gaussian_stability() { - test_samples(213, NormalInverseGaussian::new(2.0, 1.0).unwrap(), &[ - 0.6568966f32, 1.3744819, 2.216063, 0.11488572, - ]); - test_samples(213, NormalInverseGaussian::new(2.0, 1.0).unwrap(), &[ - 0.6838707059642927f64, - 2.4447306460569784, - 0.2361045023235968, - 1.7774534624785319, - ]); + test_samples( + 213, + NormalInverseGaussian::new(2.0, 1.0).unwrap(), + &[0.6568966f32, 1.3744819, 2.216063, 0.11488572], + ); + test_samples( + 213, + NormalInverseGaussian::new(2.0, 1.0).unwrap(), + &[ + 0.6838707059642927f64, + 2.4447306460569784, + 0.2361045023235968, + 1.7774534624785319, + ], + ); } #[test] fn pert_stability() { // mean = 4, var = 12/7 - test_samples(860, Pert::new(2., 10., 3.).unwrap(), &[ - 4.908681667460367, - 4.014196196158352, - 2.6489397149197234, - 3.4569780580044727, - 4.242864311947118, - ]); + test_samples( + 860, + Pert::new(2., 10., 3.).unwrap(), + &[ + 4.908681667460367, + 4.014196196158352, + 2.6489397149197234, + 3.4569780580044727, + 4.242864311947118, + ], + ); } #[test] fn inverse_gaussian_stability() { - test_samples(213, InverseGaussian::new(1.0, 3.0).unwrap(),&[ - 0.9339157f32, 1.108113, 0.50864697, 0.39849377, - ]); - test_samples(213, InverseGaussian::new(1.0, 3.0).unwrap(), &[ - 1.0707604954722476f64, - 0.9628140605340697, - 0.4069687656468226, - 0.660283852985818, - ]); + test_samples( + 213, + InverseGaussian::new(1.0, 3.0).unwrap(), + &[0.9339157f32, 1.108113, 0.50864697, 0.39849377], + ); + test_samples( + 213, + InverseGaussian::new(1.0, 3.0).unwrap(), + &[ + 1.0707604954722476f64, + 0.9628140605340697, + 0.4069687656468226, + 0.660283852985818, + ], + ); } #[test] fn gamma_stability() { // Gamma has 3 cases: shape == 1, shape < 1, shape > 1 - test_samples(223, Gamma::new(1.0, 5.0).unwrap(), &[ - 5.398085f32, 9.162783, 0.2300583, 1.7235851, - ]); - test_samples(223, Gamma::new(0.8, 5.0).unwrap(), &[ - 0.5051203f32, 0.9048302, 3.095812, 1.8566116, - ]); - test_samples(223, Gamma::new(1.1, 5.0).unwrap(), &[ - 7.783878094584059f64, - 1.4939528171618057, - 8.638017638857592, - 3.0949337228829004, - ]); + test_samples( + 223, + Gamma::new(1.0, 5.0).unwrap(), + &[5.398085f32, 9.162783, 0.2300583, 1.7235851], + ); + test_samples( + 223, + Gamma::new(0.8, 5.0).unwrap(), + &[0.5051203f32, 0.9048302, 3.095812, 1.8566116], + ); + test_samples( + 223, + Gamma::new(1.1, 5.0).unwrap(), + &[ + 7.783878094584059f64, + 1.4939528171618057, + 8.638017638857592, + 3.0949337228829004, + ], + ); // ChiSquared has 2 cases: k == 1, k != 1 - test_samples(223, ChiSquared::new(1.0).unwrap(), &[ - 0.4893526200348249f64, - 1.635249736808788, - 0.5013580219361969, - 0.1457735613733489, - ]); - test_samples(223, ChiSquared::new(0.1).unwrap(), &[ - 0.014824404726978617f64, - 0.021602123937134326, - 0.0000003431429746851693, - 0.00000002291755769542258, - ]); - test_samples(223, ChiSquared::new(10.0).unwrap(), &[ - 12.693656f32, 6.812016, 11.082001, 12.436167, - ]); + test_samples( + 223, + ChiSquared::new(1.0).unwrap(), + &[ + 0.4893526200348249f64, + 1.635249736808788, + 0.5013580219361969, + 0.1457735613733489, + ], + ); + test_samples( + 223, + ChiSquared::new(0.1).unwrap(), + &[ + 0.014824404726978617f64, + 0.021602123937134326, + 0.0000003431429746851693, + 0.00000002291755769542258, + ], + ); + test_samples( + 223, + ChiSquared::new(10.0).unwrap(), + &[12.693656f32, 6.812016, 11.082001, 12.436167], + ); // FisherF has same special cases as ChiSquared on each param - test_samples(223, FisherF::new(1.0, 13.5).unwrap(), &[ - 0.32283646f32, 0.048049655, 0.0788893, 1.817178, - ]); - test_samples(223, FisherF::new(1.0, 1.0).unwrap(), &[ - 0.29925257f32, 3.4392934, 9.567652, 0.020074, - ]); - test_samples(223, FisherF::new(0.7, 13.5).unwrap(), &[ - 3.3196593155045124f64, - 0.3409169916262829, - 0.03377989856426519, - 0.00004041672861036937, - ]); + test_samples( + 223, + FisherF::new(1.0, 13.5).unwrap(), + &[0.32283646f32, 0.048049655, 0.0788893, 1.817178], + ); + test_samples( + 223, + FisherF::new(1.0, 1.0).unwrap(), + &[0.29925257f32, 3.4392934, 9.567652, 0.020074], + ); + test_samples( + 223, + FisherF::new(0.7, 13.5).unwrap(), + &[ + 3.3196593155045124f64, + 0.3409169916262829, + 0.03377989856426519, + 0.00004041672861036937, + ], + ); // StudentT has same special cases as ChiSquared - test_samples(223, StudentT::new(1.0).unwrap(), &[ - 0.54703987f32, -1.8545331, 3.093162, -0.14168274, - ]); - test_samples(223, StudentT::new(1.1).unwrap(), &[ - 0.7729195887949754f64, - 1.2606210611616204, - -1.7553606501113175, - -2.377641221169782, - ]); + test_samples( + 223, + StudentT::new(1.0).unwrap(), + &[0.54703987f32, -1.8545331, 3.093162, -0.14168274], + ); + test_samples( + 223, + StudentT::new(1.1).unwrap(), + &[ + 0.7729195887949754f64, + 1.2606210611616204, + -1.7553606501113175, + -2.377641221169782, + ], + ); // Beta has two special cases: // // 1. min(alpha, beta) <= 1 // 2. min(alpha, beta) > 1 - test_samples(223, Beta::new(1.0, 0.8).unwrap(), &[ - 0.8300703726659456, - 0.8134131062097899, - 0.47912589330631555, - 0.25323238071138526, - ]); - test_samples(223, Beta::new(3.0, 1.2).unwrap(), &[ - 0.49563509121756827, - 0.9551305482256759, - 0.5151181353461637, - 0.7551732971235077, - ]); + test_samples( + 223, + Beta::new(1.0, 0.8).unwrap(), + &[ + 0.8300703726659456, + 0.8134131062097899, + 0.47912589330631555, + 0.25323238071138526, + ], + ); + test_samples( + 223, + Beta::new(3.0, 1.2).unwrap(), + &[ + 0.49563509121756827, + 0.9551305482256759, + 0.5151181353461637, + 0.7551732971235077, + ], + ); } #[test] fn exponential_stability() { - test_samples(223, Exp1, &[ - 1.079617f32, 1.8325565, 0.04601166, 0.34471703, - ]); - test_samples(223, Exp1, &[ - 1.0796170642388276f64, - 1.8325565304274, - 0.04601166186842716, - 0.3447170217100157, - ]); - - test_samples(223, Exp::new(2.0).unwrap(), &[ - 0.5398085f32, 0.91627824, 0.02300583, 0.17235851, - ]); - test_samples(223, Exp::new(1.0).unwrap(), &[ - 1.0796170642388276f64, - 1.8325565304274, - 0.04601166186842716, - 0.3447170217100157, - ]); + test_samples(223, Exp1, &[1.079617f32, 1.8325565, 0.04601166, 0.34471703]); + test_samples( + 223, + Exp1, + &[ + 1.0796170642388276f64, + 1.8325565304274, + 0.04601166186842716, + 0.3447170217100157, + ], + ); + + test_samples( + 223, + Exp::new(2.0).unwrap(), + &[0.5398085f32, 0.91627824, 0.02300583, 0.17235851], + ); + test_samples( + 223, + Exp::new(1.0).unwrap(), + &[ + 1.0796170642388276f64, + 1.8325565304274, + 0.04601166186842716, + 0.3447170217100157, + ], + ); } #[test] fn normal_stability() { - test_samples(213, StandardNormal, &[ - -0.11844189f32, 0.781378, 0.06563994, -1.1932899, - ]); - test_samples(213, StandardNormal, &[ - -0.11844188827977231f64, - 0.7813779637772346, - 0.06563993969580051, - -1.1932899004186373, - ]); - - test_samples(213, Normal::new(0.0, 1.0).unwrap(), &[ - -0.11844189f32, 0.781378, 0.06563994, -1.1932899, - ]); - test_samples(213, Normal::new(2.0, 0.5).unwrap(), &[ - 1.940779055860114f64, - 2.3906889818886174, - 2.0328199698479, - 1.4033550497906813, - ]); - - test_samples(213, LogNormal::new(0.0, 1.0).unwrap(), &[ - 0.88830346f32, 2.1844804, 1.0678421, 0.30322206, - ]); - test_samples(213, LogNormal::new(2.0, 0.5).unwrap(), &[ - 6.964174338639032f64, - 10.921015733601452, - 7.6355881556915906, - 4.068828213584092, - ]); + test_samples( + 213, + StandardNormal, + &[-0.11844189f32, 0.781378, 0.06563994, -1.1932899], + ); + test_samples( + 213, + StandardNormal, + &[ + -0.11844188827977231f64, + 0.7813779637772346, + 0.06563993969580051, + -1.1932899004186373, + ], + ); + + test_samples( + 213, + Normal::new(0.0, 1.0).unwrap(), + &[-0.11844189f32, 0.781378, 0.06563994, -1.1932899], + ); + test_samples( + 213, + Normal::new(2.0, 0.5).unwrap(), + &[ + 1.940779055860114f64, + 2.3906889818886174, + 2.0328199698479, + 1.4033550497906813, + ], + ); + + test_samples( + 213, + LogNormal::new(0.0, 1.0).unwrap(), + &[0.88830346f32, 2.1844804, 1.0678421, 0.30322206], + ); + test_samples( + 213, + LogNormal::new(2.0, 0.5).unwrap(), + &[ + 6.964174338639032f64, + 10.921015733601452, + 7.6355881556915906, + 4.068828213584092, + ], + ); } #[test] fn weibull_stability() { - test_samples(213, Weibull::new(1.0, 1.0).unwrap(), &[ - 0.041495778f32, 0.7531094, 1.4189332, 0.38386202, - ]); - test_samples(213, Weibull::new(2.0, 0.5).unwrap(), &[ - 1.1343478702739669f64, - 0.29470010050655226, - 0.7556151370284702, - 7.877212340241561, - ]); + test_samples( + 213, + Weibull::new(1.0, 1.0).unwrap(), + &[0.041495778f32, 0.7531094, 1.4189332, 0.38386202], + ); + test_samples( + 213, + Weibull::new(2.0, 0.5).unwrap(), + &[ + 1.1343478702739669f64, + 0.29470010050655226, + 0.7556151370284702, + 7.877212340241561, + ], + ); } #[cfg(feature = "alloc")] @@ -351,13 +505,16 @@ fn dirichlet_stability() { rng.sample(Dirichlet::new([1.0, 2.0, 3.0]).unwrap()), [0.12941567177708177, 0.4702121891675036, 0.4003721390554146] ); - assert_eq!(rng.sample(Dirichlet::new([8.0; 5]).unwrap()), [ - 0.17684200044809556, - 0.29915953935953055, - 0.1832858056608014, - 0.1425623503573967, - 0.19815030417417595 - ]); + assert_eq!( + rng.sample(Dirichlet::new([8.0; 5]).unwrap()), + [ + 0.17684200044809556, + 0.29915953935953055, + 0.1832858056608014, + 0.1425623503573967, + 0.19815030417417595 + ] + ); // Test stability for the case where all alphas are less than 0.1. assert_eq!( rng.sample(Dirichlet::new([0.05, 0.025, 0.075, 0.05]).unwrap()), @@ -372,12 +529,16 @@ fn dirichlet_stability() { #[test] fn cauchy_stability() { - test_samples(353, Cauchy::new(100f64, 10.0).unwrap(), &[ - 77.93369152808678f64, - 90.1606912098641, - 125.31516221323625, - 86.10217834773925, - ]); + test_samples( + 353, + Cauchy::new(100f64, 10.0).unwrap(), + &[ + 77.93369152808678f64, + 90.1606912098641, + 125.31516221323625, + 86.10217834773925, + ], + ); // Unfortunately this test is not fully portable due to reliance on the // system's implementation of tanf (see doc on Cauchy struct). @@ -386,7 +547,7 @@ fn cauchy_stability() { let mut rng = get_rng(353); let expected = [15.023088, -5.446413, 3.7092876, 3.112482]; for &a in expected.iter() { - let b = rng.sample(&distr); + let b = rng.sample(distr); assert_almost_eq!(a, b, 1e-5); } } diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index ecb0e56bc47..61f54c4a85d 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -15,7 +15,8 @@ const MULTIPLIER: u128 = 0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645; use core::fmt; use rand_core::{impls, le, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; /// A PCG random number generator (XSL RR 128/64 (LCG) variant). /// @@ -153,7 +154,6 @@ impl RngCore for Lcg128Xsl64 { } } - /// A PCG random number generator (XSL 128/64 (MCG) variant). /// /// Permuted Congruential Generator with 128-bit state, internal Multiplicative diff --git a/rand_pcg/src/pcg128cm.rs b/rand_pcg/src/pcg128cm.rs index 29be17a904d..6910f3458e1 100644 --- a/rand_pcg/src/pcg128cm.rs +++ b/rand_pcg/src/pcg128cm.rs @@ -15,7 +15,8 @@ const MULTIPLIER: u64 = 15750249268501108917; use core::fmt; use rand_core::{impls, le, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; /// A PCG random number generator (CM DXSM 128/64 (LCG) variant). /// diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index 0b6864a42f3..8b2df6aa618 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -12,7 +12,8 @@ use core::fmt; use rand_core::{impls, le, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; // This is the default multiplier used by PCG for 64-bit state. const MULTIPLIER: u64 = 6364136223846793005; diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index ded1e7812fb..00000000000 --- a/rustfmt.toml +++ /dev/null @@ -1,32 +0,0 @@ -# This rustfmt file is added for configuration, but in practice much of our -# code is hand-formatted, frequently with more readable results. - -# Comments: -normalize_comments = true -wrap_comments = false -comment_width = 90 # small excess is okay but prefer 80 - -# Arguments: -use_small_heuristics = "Default" -# TODO: single line functions only where short, please? -# https://github.com/rust-lang/rustfmt/issues/3358 -fn_single_line = false -fn_args_layout = "Compressed" -overflow_delimited_expr = true -where_single_line = true - -# enum_discrim_align_threshold = 20 -# struct_field_align_threshold = 20 - -# Compatibility: -edition = "2021" - -# Misc: -inline_attribute_width = 80 -blank_lines_upper_bound = 2 -reorder_impl_items = true -# report_todo = "Unnumbered" -# report_fixme = "Unnumbered" - -# Ignored files: -ignore = [] diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index a68a82965ce..a8a46b0e3cc 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -13,7 +13,7 @@ use crate::Rng; use core::fmt; #[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; /// The Bernoulli distribution. /// @@ -151,7 +151,8 @@ mod test { #[cfg(feature = "serde1")] fn test_serializing_deserializing_bernoulli() { let coin_flip = Bernoulli::new(0.5).unwrap(); - let de_coin_flip: Bernoulli = bincode::deserialize(&bincode::serialize(&coin_flip).unwrap()).unwrap(); + let de_coin_flip: Bernoulli = + bincode::deserialize(&bincode::serialize(&coin_flip).unwrap()).unwrap(); assert_eq!(coin_flip.p_int, de_coin_flip.p_int); } @@ -208,9 +209,10 @@ mod test { for x in &mut buf { *x = rng.sample(distr); } - assert_eq!(buf, [ - true, false, false, true, false, false, true, true, true, true - ]); + assert_eq!( + buf, + [true, false, false, true, false, false, true, true, true, true] + ); } #[test] diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index 0eabfb80594..2bd6a6a4044 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -10,7 +10,8 @@ //! Distribution trait and associates use crate::Rng; -#[cfg(feature = "alloc")] use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::string::String; use core::iter; /// Types (distributions) that can be used to create a random instance of `T`. diff --git a/src/distributions/float.rs b/src/distributions/float.rs index ace6fe66119..84022685817 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -8,14 +8,15 @@ //! Basic floating-point number distributions -use crate::distributions::utils::{IntAsSIMD, FloatAsSIMD, FloatSIMDUtils}; +use crate::distributions::utils::{FloatAsSIMD, FloatSIMDUtils, IntAsSIMD}; use crate::distributions::{Distribution, Standard}; use crate::Rng; use core::mem; -#[cfg(feature = "simd_support")] use core::simd::prelude::*; +#[cfg(feature = "simd_support")] +use core::simd::prelude::*; #[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; /// A distribution to sample floating point numbers uniformly in the half-open /// interval `(0, 1]`, i.e. including 1 but not 0. @@ -72,7 +73,6 @@ pub struct OpenClosed01; #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Open01; - // This trait is needed by both this lib and rand_distr hence is a hidden export #[doc(hidden)] pub trait IntoFloat { @@ -146,12 +146,11 @@ macro_rules! float_impls { // Transmute-based method; 23/52 random bits; (0, 1) interval. // We use the most significant bits because for simple RNGs // those are usually more random. - use core::$f_scalar::EPSILON; let float_size = mem::size_of::<$f_scalar>() as $u_scalar * 8; let value: $uty = rng.random(); let fraction = value >> $uty::splat(float_size - $fraction_bits); - fraction.into_float_with_exponent(0) - $ty::splat(1.0 - EPSILON / 2.0) + fraction.into_float_with_exponent(0) - $ty::splat(1.0 - $f_scalar::EPSILON / 2.0) } } } @@ -210,9 +209,15 @@ mod tests { let mut zeros = StepRng::new(0, 0); assert_eq!(zeros.sample::<$ty, _>(Open01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 9 | 1 << (9 + 32), 0); - assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / two * $ty::splat(3.0)); + assert_eq!( + one.sample::<$ty, _>(Open01), + $EPSILON / two * $ty::splat(3.0) + ); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(Open01), $ty::splat(1.0) - $EPSILON / two); + assert_eq!( + max.sample::<$ty, _>(Open01), + $ty::splat(1.0) - $EPSILON / two + ); } }; } @@ -252,9 +257,15 @@ mod tests { let mut zeros = StepRng::new(0, 0); assert_eq!(zeros.sample::<$ty, _>(Open01), $ZERO + $EPSILON / two); let mut one = StepRng::new(1 << 12, 0); - assert_eq!(one.sample::<$ty, _>(Open01), $EPSILON / two * $ty::splat(3.0)); + assert_eq!( + one.sample::<$ty, _>(Open01), + $EPSILON / two * $ty::splat(3.0) + ); let mut max = StepRng::new(!0, 0); - assert_eq!(max.sample::<$ty, _>(Open01), $ty::splat(1.0) - $EPSILON / two); + assert_eq!( + max.sample::<$ty, _>(Open01), + $ty::splat(1.0) - $EPSILON / two + ); } }; } @@ -269,7 +280,9 @@ mod tests { #[test] fn value_stability() { fn test_samples>( - distr: &D, zero: T, expected: &[T], + distr: &D, + zero: T, + expected: &[T], ) { let mut rng = crate::test::rng(0x6f44f5646c2a7334); let mut buf = [zero; 3]; @@ -280,25 +293,25 @@ mod tests { } test_samples(&Standard, 0f32, &[0.0035963655, 0.7346052, 0.09778172]); - test_samples(&Standard, 0f64, &[ - 0.7346051961657583, - 0.20298547462974248, - 0.8166436635290655, - ]); + test_samples( + &Standard, + 0f64, + &[0.7346051961657583, 0.20298547462974248, 0.8166436635290655], + ); test_samples(&OpenClosed01, 0f32, &[0.003596425, 0.73460525, 0.09778178]); - test_samples(&OpenClosed01, 0f64, &[ - 0.7346051961657584, - 0.2029854746297426, - 0.8166436635290656, - ]); + test_samples( + &OpenClosed01, + 0f64, + &[0.7346051961657584, 0.2029854746297426, 0.8166436635290656], + ); test_samples(&Open01, 0f32, &[0.0035963655, 0.73460525, 0.09778172]); - test_samples(&Open01, 0f64, &[ - 0.7346051961657584, - 0.20298547462974248, - 0.8166436635290656, - ]); + test_samples( + &Open01, + 0f64, + &[0.7346051961657584, 0.20298547462974248, 0.8166436635290656], + ); #[cfg(feature = "simd_support")] { @@ -306,17 +319,25 @@ mod tests { // non-SIMD types; we assume this pattern continues across all // SIMD types. - test_samples(&Standard, f32x2::from([0.0, 0.0]), &[ - f32x2::from([0.0035963655, 0.7346052]), - f32x2::from([0.09778172, 0.20298547]), - f32x2::from([0.34296435, 0.81664366]), - ]); - - test_samples(&Standard, f64x2::from([0.0, 0.0]), &[ - f64x2::from([0.7346051961657583, 0.20298547462974248]), - f64x2::from([0.8166436635290655, 0.7423708925400552]), - f64x2::from([0.16387782224016323, 0.9087068770169618]), - ]); + test_samples( + &Standard, + f32x2::from([0.0, 0.0]), + &[ + f32x2::from([0.0035963655, 0.7346052]), + f32x2::from([0.09778172, 0.20298547]), + f32x2::from([0.34296435, 0.81664366]), + ], + ); + + test_samples( + &Standard, + f64x2::from([0.0, 0.0]), + &[ + f64x2::from([0.7346051961657583, 0.20298547462974248]), + f64x2::from([0.8166436635290655, 0.7423708925400552]), + f64x2::from([0.16387782224016323, 0.9087068770169618]), + ], + ); } } } diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index ca27c6331d8..60bcbf9565f 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -19,10 +19,11 @@ use core::arch::x86_64::__m512i; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::{__m128i, __m256i}; use core::num::{ - NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,NonZeroU128, - NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize,NonZeroI128 + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, }; -#[cfg(feature = "simd_support")] use core::simd::*; +#[cfg(feature = "simd_support")] +use core::simd::*; impl Distribution for Standard { #[inline] @@ -211,7 +212,9 @@ mod tests { #[test] fn value_stability() { fn test_samples(zero: T, expected: &[T]) - where Standard: Distribution { + where + Standard: Distribution, + { let mut rng = crate::test::rng(807); let mut buf = [zero; 3]; for x in &mut buf { @@ -223,24 +226,33 @@ mod tests { test_samples(0u8, &[9, 247, 111]); test_samples(0u16, &[32265, 42999, 38255]); test_samples(0u32, &[2220326409, 2575017975, 2018088303]); - test_samples(0u64, &[ - 11059617991457472009, - 16096616328739788143, - 1487364411147516184, - ]); - test_samples(0u128, &[ - 296930161868957086625409848350820761097, - 145644820879247630242265036535529306392, - 111087889832015897993126088499035356354, - ]); + test_samples( + 0u64, + &[ + 11059617991457472009, + 16096616328739788143, + 1487364411147516184, + ], + ); + test_samples( + 0u128, + &[ + 296930161868957086625409848350820761097, + 145644820879247630242265036535529306392, + 111087889832015897993126088499035356354, + ], + ); #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] test_samples(0usize, &[2220326409, 2575017975, 2018088303]); #[cfg(target_pointer_width = "64")] - test_samples(0usize, &[ - 11059617991457472009, - 16096616328739788143, - 1487364411147516184, - ]); + test_samples( + 0usize, + &[ + 11059617991457472009, + 16096616328739788143, + 1487364411147516184, + ], + ); test_samples(0i8, &[9, -9, 111]); // Skip further i* types: they are simple reinterpretation of u* samples @@ -249,49 +261,58 @@ mod tests { { // We only test a sub-set of types here and make assumptions about the rest. - test_samples(u8x4::default(), &[ - u8x4::from([9, 126, 87, 132]), - u8x4::from([247, 167, 123, 153]), - u8x4::from([111, 149, 73, 120]), - ]); - test_samples(u8x8::default(), &[ - u8x8::from([9, 126, 87, 132, 247, 167, 123, 153]), - u8x8::from([111, 149, 73, 120, 68, 171, 98, 223]), - u8x8::from([24, 121, 1, 50, 13, 46, 164, 20]), - ]); + test_samples( + u8x4::default(), + &[ + u8x4::from([9, 126, 87, 132]), + u8x4::from([247, 167, 123, 153]), + u8x4::from([111, 149, 73, 120]), + ], + ); + test_samples( + u8x8::default(), + &[ + u8x8::from([9, 126, 87, 132, 247, 167, 123, 153]), + u8x8::from([111, 149, 73, 120, 68, 171, 98, 223]), + u8x8::from([24, 121, 1, 50, 13, 46, 164, 20]), + ], + ); - test_samples(i64x8::default(), &[ - i64x8::from([ - -7387126082252079607, - -2350127744969763473, - 1487364411147516184, - 7895421560427121838, - 602190064936008898, - 6022086574635100741, - -5080089175222015595, - -4066367846667249123, - ]), - i64x8::from([ - 9180885022207963908, - 3095981199532211089, - 6586075293021332726, - 419343203796414657, - 3186951873057035255, - 5287129228749947252, - 444726432079249540, - -1587028029513790706, - ]), - i64x8::from([ - 6075236523189346388, - 1351763722368165432, - -6192309979959753740, - -7697775502176768592, - -4482022114172078123, - 7522501477800909500, - -1837258847956201231, - -586926753024886735, - ]), - ]); + test_samples( + i64x8::default(), + &[ + i64x8::from([ + -7387126082252079607, + -2350127744969763473, + 1487364411147516184, + 7895421560427121838, + 602190064936008898, + 6022086574635100741, + -5080089175222015595, + -4066367846667249123, + ]), + i64x8::from([ + 9180885022207963908, + 3095981199532211089, + 6586075293021332726, + 419343203796414657, + 3186951873057035255, + 5287129228749947252, + 444726432079249540, + -1587028029513790706, + ]), + i64x8::from([ + 6075236523189346388, + 1351763722368165432, + -6192309979959753740, + -7697775502176768592, + -4482022114172078123, + 7522501477800909500, + -1837258847956201231, + -586926753024886735, + ]), + ], + ); } } } diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 91303428875..e5973297dd9 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -110,10 +110,10 @@ pub mod hidden_export { pub mod uniform; pub use self::bernoulli::{Bernoulli, BernoulliError}; -pub use self::distribution::{Distribution, DistIter, DistMap}; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use self::distribution::DistString; +pub use self::distribution::{DistIter, DistMap, Distribution}; pub use self::float::{Open01, OpenClosed01}; pub use self::other::Alphanumeric; pub use self::slice::Slice; diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 7cb63cc8365..15ccf30a8c9 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -8,24 +8,23 @@ //! The implementations of the `Standard` distribution for other built-in types. -use core::char; -use core::num::Wrapping; #[cfg(feature = "alloc")] use alloc::string::String; +use core::char; +use core::num::Wrapping; -use crate::distributions::{Distribution, Standard, Uniform}; #[cfg(feature = "alloc")] use crate::distributions::DistString; +use crate::distributions::{Distribution, Standard, Uniform}; use crate::Rng; -#[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; use core::mem::{self, MaybeUninit}; #[cfg(feature = "simd_support")] use core::simd::prelude::*; #[cfg(feature = "simd_support")] use core::simd::{LaneCount, MaskElement, SupportedLaneCount}; - +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; // ----- Sampling distributions ----- @@ -71,7 +70,6 @@ use core::simd::{LaneCount, MaskElement, SupportedLaneCount}; #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Alphanumeric; - // ----- Implementations of distributions ----- impl Distribution for Standard { @@ -240,7 +238,8 @@ macro_rules! tuple_impls { tuple_impls! {A B C D E F G H I J K L} impl Distribution<[T; N]> for Standard -where Standard: Distribution +where + Standard: Distribution, { #[inline] fn sample(&self, _rng: &mut R) -> [T; N] { @@ -255,7 +254,8 @@ where Standard: Distribution } impl Distribution> for Standard -where Standard: Distribution +where + Standard: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> Option { @@ -269,7 +269,8 @@ where Standard: Distribution } impl Distribution> for Standard -where Standard: Distribution +where + Standard: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> Wrapping { @@ -277,7 +278,6 @@ where Standard: Distribution } } - #[cfg(test)] mod tests { use super::*; @@ -315,9 +315,7 @@ mod tests { let mut incorrect = false; for _ in 0..100 { let c: char = rng.sample(Alphanumeric).into(); - incorrect |= !(('0'..='9').contains(&c) || - ('A'..='Z').contains(&c) || - ('a'..='z').contains(&c) ); + incorrect |= !c.is_ascii_alphanumeric(); } assert!(!incorrect); } @@ -325,7 +323,9 @@ mod tests { #[test] fn value_stability() { fn test_samples>( - distr: &D, zero: T, expected: &[T], + distr: &D, + zero: T, + expected: &[T], ) { let mut rng = crate::test::rng(807); let mut buf = [zero; 5]; @@ -335,54 +335,66 @@ mod tests { assert_eq!(&buf, expected); } - test_samples(&Standard, 'a', &[ - '\u{8cdac}', - '\u{a346a}', - '\u{80120}', - '\u{ed692}', - '\u{35888}', - ]); + test_samples( + &Standard, + 'a', + &[ + '\u{8cdac}', + '\u{a346a}', + '\u{80120}', + '\u{ed692}', + '\u{35888}', + ], + ); test_samples(&Alphanumeric, 0, &[104, 109, 101, 51, 77]); test_samples(&Standard, false, &[true, true, false, true, false]); - test_samples(&Standard, None as Option, &[ - Some(true), - None, - Some(false), - None, - Some(false), - ]); - test_samples(&Standard, Wrapping(0i32), &[ - Wrapping(-2074640887), - Wrapping(-1719949321), - Wrapping(2018088303), - Wrapping(-547181756), - Wrapping(838957336), - ]); + test_samples( + &Standard, + None as Option, + &[Some(true), None, Some(false), None, Some(false)], + ); + test_samples( + &Standard, + Wrapping(0i32), + &[ + Wrapping(-2074640887), + Wrapping(-1719949321), + Wrapping(2018088303), + Wrapping(-547181756), + Wrapping(838957336), + ], + ); // We test only sub-sets of tuple and array impls test_samples(&Standard, (), &[(), (), (), (), ()]); - test_samples(&Standard, (false,), &[ - (true,), - (true,), + test_samples( + &Standard, (false,), - (true,), - (false,), - ]); - test_samples(&Standard, (false, false), &[ - (true, true), - (false, true), - (false, false), - (true, false), + &[(true,), (true,), (false,), (true,), (false,)], + ); + test_samples( + &Standard, (false, false), - ]); + &[ + (true, true), + (false, true), + (false, false), + (true, false), + (false, false), + ], + ); test_samples(&Standard, [0u8; 0], &[[], [], [], [], []]); - test_samples(&Standard, [0u8; 3], &[ - [9, 247, 111], - [68, 24, 13], - [174, 19, 194], - [172, 69, 213], - [149, 207, 29], - ]); + test_samples( + &Standard, + [0u8; 3], + &[ + [9, 247, 111], + [68, 24, 13], + [174, 19, 194], + [172, 69, 213], + [149, 207, 29], + ], + ); } } diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index d49f45ccebc..88cff8897bb 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -148,7 +148,11 @@ impl<'a> super::DistString for Slice<'a, char> { // Split the extension of string to reuse the unused capacities. // Skip the split for small length or only ascii slice. - let mut extend_len = if max_char_len == 1 || len < 100 { len } else { len / 4 }; + let mut extend_len = if max_char_len == 1 || len < 100 { + len + } else { + len / 4 + }; let mut remain_len = len; while extend_len > 0 { string.reserve(max_char_len * extend_len); diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 7fbf0fced25..fde8d6dbbec 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -104,18 +104,22 @@ //! [`SampleBorrow::borrow`]: crate::distributions::uniform::SampleBorrow::borrow use core::fmt; -use core::time::Duration; use core::ops::{Range, RangeInclusive}; +use core::time::Duration; use crate::distributions::float::IntoFloat; -use crate::distributions::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD, WideningMultiply}; +use crate::distributions::utils::{ + BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD, WideningMultiply, +}; use crate::distributions::Distribution; #[cfg(feature = "simd_support")] use crate::distributions::Standard; use crate::{Rng, RngCore}; -#[cfg(feature = "simd_support")] use core::simd::prelude::*; -#[cfg(feature = "simd_support")] use core::simd::{LaneCount, SupportedLaneCount}; +#[cfg(feature = "simd_support")] +use core::simd::prelude::*; +#[cfg(feature = "simd_support")] +use core::simd::{LaneCount, SupportedLaneCount}; /// Error type returned from [`Uniform::new`] and `new_inclusive`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -140,7 +144,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} #[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; /// Sample values uniformly between two bounds. /// @@ -194,7 +198,10 @@ use serde::{Serialize, Deserialize}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] -#[cfg_attr(feature = "serde1", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")))] +#[cfg_attr( + feature = "serde1", + serde(bound(deserialize = "X::Sampler: Deserialize<'de>")) +)] pub struct Uniform(X::Sampler); impl Uniform { @@ -297,7 +304,11 @@ pub trait UniformSampler: Sized { /// ::Sampler::sample_single(lb, ub, &mut rng).unwrap() /// } /// ``` - fn sample_single(low: B1, high: B2, rng: &mut R) -> Result + fn sample_single( + low: B1, + high: B2, + rng: &mut R, + ) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -314,10 +325,14 @@ pub trait UniformSampler: Sized { /// some types more optimal implementations for single usage may be provided /// via this method. /// Results may not be identical. - fn sample_single_inclusive(low: B1, high: B2, rng: &mut R) - -> Result - where B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized + fn sample_single_inclusive( + low: B1, + high: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, { let uniform: Self = UniformSampler::new_inclusive(low, high)?; Ok(uniform.sample(rng)) @@ -340,7 +355,6 @@ impl TryFrom> for Uniform { } } - /// Helper trait similar to [`Borrow`] but implemented /// only for SampleUniform and references to SampleUniform in /// order to resolve ambiguity issues. @@ -353,7 +367,8 @@ pub trait SampleBorrow { fn borrow(&self) -> &Borrowed; } impl SampleBorrow for Borrowed -where Borrowed: SampleUniform +where + Borrowed: SampleUniform, { #[inline(always)] fn borrow(&self) -> &Borrowed { @@ -361,7 +376,8 @@ where Borrowed: SampleUniform } } impl<'a, Borrowed> SampleBorrow for &'a Borrowed -where Borrowed: SampleUniform +where + Borrowed: SampleUniform, { #[inline(always)] fn borrow(&self) -> &Borrowed { @@ -405,12 +421,10 @@ impl SampleRange for RangeInclusive { } } - //////////////////////////////////////////////////////////////////////////////// // What follows are all back-ends. - /// The back-end implementing [`UniformSampler`] for integer types. /// /// Unless you are implementing [`UniformSampler`] for your own type, this type @@ -505,7 +519,7 @@ macro_rules! uniform_int_impl { Ok(UniformInt { low, - range: range as $ty, // type: $uty + range: range as $ty, // type: $uty thresh: thresh as $uty as $ty, // type: $sample_ty }) } @@ -529,7 +543,11 @@ macro_rules! uniform_int_impl { } #[inline] - fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Result + fn sample_single( + low_b: B1, + high_b: B2, + rng: &mut R, + ) -> Result where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, @@ -549,7 +567,9 @@ macro_rules! uniform_int_impl { #[cfg(not(feature = "unbiased"))] #[inline] fn sample_single_inclusive( - low_b: B1, high_b: B2, rng: &mut R, + low_b: B1, + high_b: B2, + rng: &mut R, ) -> Result where B1: SampleBorrow + Sized, @@ -585,7 +605,9 @@ macro_rules! uniform_int_impl { #[cfg(feature = "unbiased")] #[inline] fn sample_single_inclusive( - low_b: B1, high_b: B2, rng: &mut R, + low_b: B1, + high_b: B2, + rng: &mut R, ) -> Result where B1: SampleBorrow<$ty> + Sized, @@ -599,7 +621,7 @@ macro_rules! uniform_int_impl { let range = high.wrapping_sub(low).wrapping_add(1) as $uty as $sample_ty; if range == 0 { // Range is MAX+1 (unrepresentable), so we need a special case - return Ok(rng.gen()); + return Ok(rng.random()); } let (mut result, mut lo) = rng.random::<$sample_ty>().wmul(range); @@ -844,7 +866,12 @@ impl UniformSampler for UniformChar { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl super::DistString for Uniform { - fn append_string(&self, rng: &mut R, string: &mut alloc::string::String, len: usize) { + fn append_string( + &self, + rng: &mut R, + string: &mut alloc::string::String, + len: usize, + ) { // Getting the hi value to assume the required length to reserve in string. let mut hi = self.0.sampler.low + self.0.sampler.range - 1; if hi >= CHAR_SURROGATE_START { @@ -911,7 +938,7 @@ macro_rules! uniform_float_impl { return Err(Error::EmptyRange); } let max_rand = <$ty>::splat( - (::core::$u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, + ($u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, ); let mut scale = high - low; @@ -947,7 +974,7 @@ macro_rules! uniform_float_impl { return Err(Error::EmptyRange); } let max_rand = <$ty>::splat( - (::core::$u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, + ($u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, ); let mut scale = (high - low) / max_rand; @@ -1111,7 +1138,6 @@ uniform_float_impl! { feature = "simd_support", f64x4, u64x4, f64, u64, 64 - 52 #[cfg(feature = "simd_support")] uniform_float_impl! { feature = "simd_support", f64x8, u64x8, f64, u64, 64 - 52 } - /// The back-end implementing [`UniformSampler`] for `Duration`. /// /// Unless you are implementing [`UniformSampler`] for your own types, this type @@ -1248,26 +1274,29 @@ impl UniformSampler for UniformDuration { #[cfg(test)] mod tests { use super::*; - use crate::rngs::mock::StepRng; use crate::distributions::utils::FloatSIMDScalarUtils; + use crate::rngs::mock::StepRng; #[test] #[cfg(feature = "serde1")] fn test_serialization_uniform_duration() { let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)).unwrap(); - let de_distr: UniformDuration = bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); + let de_distr: UniformDuration = + bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); assert_eq!(distr, de_distr); } #[test] #[cfg(feature = "serde1")] fn test_uniform_serialization() { - let unit_box: Uniform = Uniform::new(-1, 1).unwrap(); - let de_unit_box: Uniform = bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); + let unit_box: Uniform = Uniform::new(-1, 1).unwrap(); + let de_unit_box: Uniform = + bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); assert_eq!(unit_box.0, de_unit_box.0); let unit_box: Uniform = Uniform::new(-1., 1.).unwrap(); - let de_unit_box: Uniform = bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); + let de_unit_box: Uniform = + bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); assert_eq!(unit_box.0, de_unit_box.0); } @@ -1293,10 +1322,6 @@ mod tests { #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_integers() { - use core::{i128, u128}; - use core::{i16, i32, i64, i8, isize}; - use core::{u16, u32, u64, u8, usize}; - let mut rng = crate::test::rng(251); macro_rules! t { ($ty:ident, $v:expr, $le:expr, $lt:expr) => {{ @@ -1383,14 +1408,15 @@ mod tests { let mut max = core::char::from_u32(0).unwrap(); for _ in 0..100 { let c = rng.gen_range('A'..='Z'); - assert!(('A'..='Z').contains(&c)); + assert!(c.is_ascii_uppercase()); max = max.max(c); } assert_eq!(max, 'Z'); let d = Uniform::new( core::char::from_u32(0xD7F0).unwrap(), core::char::from_u32(0xE010).unwrap(), - ).unwrap(); + ) + .unwrap(); for _ in 0..100 { let c = d.sample(&mut rng); assert!((c as u32) < 0xD800 || (c as u32) > 0xDFFF); @@ -1403,12 +1429,16 @@ mod tests { let string2 = Uniform::new( core::char::from_u32(0x0000).unwrap(), core::char::from_u32(0x0080).unwrap(), - ).unwrap().sample_string(&mut rng, 100); + ) + .unwrap() + .sample_string(&mut rng, 100); assert_eq!(string2.capacity(), 100); let string3 = Uniform::new_inclusive( core::char::from_u32(0x0000).unwrap(), core::char::from_u32(0x0080).unwrap(), - ).unwrap().sample_string(&mut rng, 100); + ) + .unwrap() + .sample_string(&mut rng, 100); assert_eq!(string3.capacity(), 200); } } @@ -1430,8 +1460,8 @@ mod tests { (-<$f_scalar>::from_bits(10), -<$f_scalar>::from_bits(1)), (-<$f_scalar>::from_bits(5), 0.0), (-<$f_scalar>::from_bits(7), -0.0), - (0.1 * ::core::$f_scalar::MAX, ::core::$f_scalar::MAX), - (-::core::$f_scalar::MAX * 0.2, ::core::$f_scalar::MAX * 0.7), + (0.1 * $f_scalar::MAX, $f_scalar::MAX), + (-$f_scalar::MAX * 0.2, $f_scalar::MAX * 0.7), ]; for &(low_scalar, high_scalar) in v.iter() { for lane in 0..<$ty>::LEN { @@ -1444,27 +1474,47 @@ mod tests { assert!(low_scalar <= v && v < high_scalar); let v = rng.sample(my_incl_uniform).extract(lane); assert!(low_scalar <= v && v <= high_scalar); - let v = <$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut rng).unwrap().extract(lane); + let v = + <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng) + .unwrap() + .extract(lane); assert!(low_scalar <= v && v < high_scalar); - let v = <$ty as SampleUniform>::Sampler - ::sample_single_inclusive(low, high, &mut rng).unwrap().extract(lane); + let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive( + low, high, &mut rng, + ) + .unwrap() + .extract(lane); assert!(low_scalar <= v && v <= high_scalar); } assert_eq!( - rng.sample(Uniform::new_inclusive(low, low).unwrap()).extract(lane), + rng.sample(Uniform::new_inclusive(low, low).unwrap()) + .extract(lane), low_scalar ); assert_eq!(zero_rng.sample(my_uniform).extract(lane), low_scalar); assert_eq!(zero_rng.sample(my_incl_uniform).extract(lane), low_scalar); - assert_eq!(<$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut zero_rng).unwrap() - .extract(lane), low_scalar); - assert_eq!(<$ty as SampleUniform>::Sampler - ::sample_single_inclusive(low, high, &mut zero_rng).unwrap() - .extract(lane), low_scalar); + assert_eq!( + <$ty as SampleUniform>::Sampler::sample_single( + low, + high, + &mut zero_rng + ) + .unwrap() + .extract(lane), + low_scalar + ); + assert_eq!( + <$ty as SampleUniform>::Sampler::sample_single_inclusive( + low, + high, + &mut zero_rng + ) + .unwrap() + .extract(lane), + low_scalar + ); assert!(max_rng.sample(my_uniform).extract(lane) < high_scalar); assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar); @@ -1472,9 +1522,16 @@ mod tests { // assert!(<$ty as SampleUniform>::Sampler // ::sample_single(low, high, &mut max_rng).unwrap() // .extract(lane) < high_scalar); - assert!(<$ty as SampleUniform>::Sampler - ::sample_single_inclusive(low, high, &mut max_rng).unwrap() - .extract(lane) <= high_scalar); + assert!( + <$ty as SampleUniform>::Sampler::sample_single_inclusive( + low, + high, + &mut max_rng + ) + .unwrap() + .extract(lane) + <= high_scalar + ); // Don't run this test for really tiny differences between high and low // since for those rounding might result in selecting high for a very @@ -1485,27 +1542,26 @@ mod tests { (-1i64 << $bits_shifted) as u64, ); assert!( - <$ty as SampleUniform>::Sampler - ::sample_single(low, high, &mut lowering_max_rng).unwrap() - .extract(lane) < high_scalar + <$ty as SampleUniform>::Sampler::sample_single( + low, + high, + &mut lowering_max_rng + ) + .unwrap() + .extract(lane) + < high_scalar ); } } } assert_eq!( - rng.sample(Uniform::new_inclusive( - ::core::$f_scalar::MAX, - ::core::$f_scalar::MAX - ).unwrap()), - ::core::$f_scalar::MAX + rng.sample(Uniform::new_inclusive($f_scalar::MAX, $f_scalar::MAX).unwrap()), + $f_scalar::MAX ); assert_eq!( - rng.sample(Uniform::new_inclusive( - -::core::$f_scalar::MAX, - -::core::$f_scalar::MAX - ).unwrap()), - -::core::$f_scalar::MAX + rng.sample(Uniform::new_inclusive(-$f_scalar::MAX, -$f_scalar::MAX).unwrap()), + -$f_scalar::MAX ); }}; } @@ -1549,21 +1605,18 @@ mod tests { macro_rules! t { ($ty:ident, $f_scalar:ident) => {{ let v: &[($f_scalar, $f_scalar)] = &[ - (::std::$f_scalar::NAN, 0.0), - (1.0, ::std::$f_scalar::NAN), - (::std::$f_scalar::NAN, ::std::$f_scalar::NAN), + ($f_scalar::NAN, 0.0), + (1.0, $f_scalar::NAN), + ($f_scalar::NAN, $f_scalar::NAN), (1.0, 0.5), - (::std::$f_scalar::MAX, -::std::$f_scalar::MAX), - (::std::$f_scalar::INFINITY, ::std::$f_scalar::INFINITY), - ( - ::std::$f_scalar::NEG_INFINITY, - ::std::$f_scalar::NEG_INFINITY, - ), - (::std::$f_scalar::NEG_INFINITY, 5.0), - (5.0, ::std::$f_scalar::INFINITY), - (::std::$f_scalar::NAN, ::std::$f_scalar::INFINITY), - (::std::$f_scalar::NEG_INFINITY, ::std::$f_scalar::NAN), - (::std::$f_scalar::NEG_INFINITY, ::std::$f_scalar::INFINITY), + ($f_scalar::MAX, -$f_scalar::MAX), + ($f_scalar::INFINITY, $f_scalar::INFINITY), + ($f_scalar::NEG_INFINITY, $f_scalar::NEG_INFINITY), + ($f_scalar::NEG_INFINITY, 5.0), + (5.0, $f_scalar::INFINITY), + ($f_scalar::NAN, $f_scalar::INFINITY), + ($f_scalar::NEG_INFINITY, $f_scalar::NAN), + ($f_scalar::NEG_INFINITY, $f_scalar::INFINITY), ]; for &(low_scalar, high_scalar) in v.iter() { for lane in 0..<$ty>::LEN { @@ -1593,7 +1646,6 @@ mod tests { } } - #[test] #[cfg_attr(miri, ignore)] // Miri is too slow fn test_durations() { @@ -1602,10 +1654,7 @@ mod tests { let v = &[ (Duration::new(10, 50000), Duration::new(100, 1234)), (Duration::new(0, 100), Duration::new(1, 50)), - ( - Duration::new(0, 0), - Duration::new(u64::MAX, 999_999_999), - ), + (Duration::new(0, 0), Duration::new(u64::MAX, 999_999_999)), ]; for &(low, high) in v.iter() { let my_uniform = Uniform::new(low, high).unwrap(); @@ -1707,8 +1756,13 @@ mod tests { #[test] fn value_stability() { fn test_samples( - lb: T, ub: T, expected_single: &[T], expected_multiple: &[T], - ) where Uniform: Distribution { + lb: T, + ub: T, + expected_single: &[T], + expected_multiple: &[T], + ) where + Uniform: Distribution, + { let mut rng = crate::test::rng(897); let mut buf = [lb; 3]; @@ -1730,11 +1784,12 @@ mod tests { test_samples(11u8, 219, &[17, 66, 214], &[181, 93, 165]); test_samples(11u32, 219, &[17, 66, 214], &[181, 93, 165]); - test_samples(0f32, 1e-2f32, &[0.0003070104, 0.0026630748, 0.00979833], &[ - 0.008194133, - 0.00398172, - 0.007428536, - ]); + test_samples( + 0f32, + 1e-2f32, + &[0.0003070104, 0.0026630748, 0.00979833], + &[0.008194133, 0.00398172, 0.007428536], + ); test_samples( -1e10f64, 1e10f64, @@ -1760,9 +1815,15 @@ mod tests { #[test] fn uniform_distributions_can_be_compared() { - assert_eq!(Uniform::new(1.0, 2.0).unwrap(), Uniform::new(1.0, 2.0).unwrap()); + assert_eq!( + Uniform::new(1.0, 2.0).unwrap(), + Uniform::new(1.0, 2.0).unwrap() + ); // To cover UniformInt - assert_eq!(Uniform::new(1_u32, 2_u32).unwrap(), Uniform::new(1_u32, 2_u32).unwrap()); + assert_eq!( + Uniform::new(1_u32, 2_u32).unwrap(), + Uniform::new(1_u32, 2_u32).unwrap() + ); } } diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index e3ef5bcdb8b..aee92b67902 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -8,9 +8,10 @@ //! Math helper functions -#[cfg(feature = "simd_support")] use core::simd::prelude::*; -#[cfg(feature = "simd_support")] use core::simd::{LaneCount, SimdElement, SupportedLaneCount}; - +#[cfg(feature = "simd_support")] +use core::simd::prelude::*; +#[cfg(feature = "simd_support")] +use core::simd::{LaneCount, SimdElement, SupportedLaneCount}; pub(crate) trait WideningMultiply { type Output; @@ -146,8 +147,10 @@ wmul_impl_usize! { u64 } #[cfg(feature = "simd_support")] mod simd_wmul { use super::*; - #[cfg(target_arch = "x86")] use core::arch::x86::*; - #[cfg(target_arch = "x86_64")] use core::arch::x86_64::*; + #[cfg(target_arch = "x86")] + use core::arch::x86::*; + #[cfg(target_arch = "x86_64")] + use core::arch::x86_64::*; wmul_impl! { (u8x4, u16x4), @@ -340,12 +343,12 @@ macro_rules! scalar_float_impl { scalar_float_impl!(f32, u32); scalar_float_impl!(f64, u64); - #[cfg(feature = "simd_support")] macro_rules! simd_impl { ($fty:ident, $uty:ident) => { impl FloatSIMDUtils for Simd<$fty, LANES> - where LaneCount: SupportedLaneCount + where + LaneCount: SupportedLaneCount, { type Mask = Mask<<$fty as SimdElement>::Mask, LANES>; type UInt = Simd<$uty, LANES>; diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index cec292dd8a8..a021376639f 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -108,7 +108,11 @@ impl WeightedIndex { X: Weight, { let mut iter = weights.into_iter(); - let mut total_weight: X = iter.next().ok_or(WeightError::InvalidInput)?.borrow().clone(); + let mut total_weight: X = iter + .next() + .ok_or(WeightError::InvalidInput)? + .borrow() + .clone(); let zero = X::ZERO; if !(total_weight >= zero) { @@ -252,9 +256,9 @@ pub struct WeightedIndexIter<'a, X: SampleUniform + PartialOrd> { } impl<'a, X> Debug for WeightedIndexIter<'a, X> - where - X: SampleUniform + PartialOrd + Debug, - X::Sampler: Debug, +where + X: SampleUniform + PartialOrd + Debug, + X::Sampler: Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeightedIndexIter") @@ -278,10 +282,7 @@ where impl<'a, X> Iterator for WeightedIndexIter<'a, X> where - X: for<'b> core::ops::SubAssign<&'b X> - + SampleUniform - + PartialOrd - + Clone, + X: for<'b> core::ops::SubAssign<&'b X> + SampleUniform + PartialOrd + Clone, { type Item = X; @@ -315,15 +316,16 @@ impl WeightedIndex { /// ``` pub fn weight(&self, index: usize) -> Option where - X: for<'a> core::ops::SubAssign<&'a X> + X: for<'a> core::ops::SubAssign<&'a X>, { - let mut weight = if index < self.cumulative_weights.len() { - self.cumulative_weights[index].clone() - } else if index == self.cumulative_weights.len() { - self.total_weight.clone() - } else { - return None; + use core::cmp::Ordering::*; + + let mut weight = match index.cmp(&self.cumulative_weights.len()) { + Less => self.cumulative_weights[index].clone(), + Equal => self.total_weight.clone(), + Greater => return None, }; + if index > 0 { weight -= &self.cumulative_weights[index - 1]; } @@ -348,7 +350,7 @@ impl WeightedIndex { /// ``` pub fn weights(&self) -> WeightedIndexIter<'_, X> where - X: for<'a> core::ops::SubAssign<&'a X> + X: for<'a> core::ops::SubAssign<&'a X>, { WeightedIndexIter { weighted_index: self, @@ -387,6 +389,7 @@ pub trait Weight: Clone { /// - `Result::Err`: Returns an error when `Self` cannot represent the /// result of `self + v` (i.e. overflow). The value of `self` should be /// discarded. + #[allow(clippy::result_unit_err)] fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()>; } @@ -417,6 +420,7 @@ macro_rules! impl_weight_float { ($t:ty) => { impl Weight for $t { const ZERO: Self = 0.0; + fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()> { // Floats have an explicit representation for overflow *self += *v; @@ -435,7 +439,7 @@ mod test { #[cfg(feature = "serde1")] #[test] fn test_weightedindex_serde1() { - let weighted_index = WeightedIndex::new(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).unwrap(); + let weighted_index = WeightedIndex::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).unwrap(); let ser_weighted_index = bincode::serialize(&weighted_index).unwrap(); let de_weighted_index: WeightedIndex = @@ -451,20 +455,20 @@ mod test { #[test] fn test_accepting_nan() { assert_eq!( - WeightedIndex::new(&[f32::NAN, 0.5]).unwrap_err(), + WeightedIndex::new([f32::NAN, 0.5]).unwrap_err(), WeightError::InvalidWeight, ); assert_eq!( - WeightedIndex::new(&[f32::NAN]).unwrap_err(), + WeightedIndex::new([f32::NAN]).unwrap_err(), WeightError::InvalidWeight, ); assert_eq!( - WeightedIndex::new(&[0.5, f32::NAN]).unwrap_err(), + WeightedIndex::new([0.5, f32::NAN]).unwrap_err(), WeightError::InvalidWeight, ); assert_eq!( - WeightedIndex::new(&[0.5, 7.0]) + WeightedIndex::new([0.5, 7.0]) .unwrap() .update_weights(&[(0, &f32::NAN)]) .unwrap_err(), @@ -516,10 +520,10 @@ mod test { verify(chosen); for _ in 0..5 { - assert_eq!(WeightedIndex::new(&[0, 1]).unwrap().sample(&mut r), 1); - assert_eq!(WeightedIndex::new(&[1, 0]).unwrap().sample(&mut r), 0); + assert_eq!(WeightedIndex::new([0, 1]).unwrap().sample(&mut r), 1); + assert_eq!(WeightedIndex::new([1, 0]).unwrap().sample(&mut r), 0); assert_eq!( - WeightedIndex::new(&[0, 0, 0, 0, 10, 0]) + WeightedIndex::new([0, 0, 0, 0, 10, 0]) .unwrap() .sample(&mut r), 4 @@ -531,19 +535,19 @@ mod test { WeightError::InvalidInput ); assert_eq!( - WeightedIndex::new(&[0]).unwrap_err(), + WeightedIndex::new([0]).unwrap_err(), WeightError::InsufficientNonZero ); assert_eq!( - WeightedIndex::new(&[10, 20, -1, 30]).unwrap_err(), + WeightedIndex::new([10, 20, -1, 30]).unwrap_err(), WeightError::InvalidWeight ); assert_eq!( - WeightedIndex::new(&[-10, 20, 1, 30]).unwrap_err(), + WeightedIndex::new([-10, 20, 1, 30]).unwrap_err(), WeightError::InvalidWeight ); assert_eq!( - WeightedIndex::new(&[-10]).unwrap_err(), + WeightedIndex::new([-10]).unwrap_err(), WeightError::InvalidWeight ); } @@ -649,7 +653,9 @@ mod test { #[test] fn value_stability() { fn test_samples( - weights: I, buf: &mut [usize], expected: &[usize], + weights: I, + buf: &mut [usize], + expected: &[usize], ) where I: IntoIterator, I::Item: SampleBorrow, @@ -665,17 +671,17 @@ mod test { let mut buf = [0; 10]; test_samples( - &[1i32, 1, 1, 1, 1, 1, 1, 1, 1], + [1i32, 1, 1, 1, 1, 1, 1, 1, 1], &mut buf, &[0, 6, 2, 6, 3, 4, 7, 8, 2, 5], ); test_samples( - &[0.7f32, 0.1, 0.1, 0.1], + [0.7f32, 0.1, 0.1, 0.1], &mut buf, &[0, 0, 0, 1, 0, 0, 2, 3, 0, 0], ); test_samples( - &[1.0f64, 0.999, 0.998, 0.997], + [1.0f64, 0.999, 0.998, 0.997], &mut buf, &[2, 2, 1, 3, 2, 1, 3, 3, 2, 1], ); @@ -683,7 +689,7 @@ mod test { #[test] fn weighted_index_distributions_can_be_compared() { - assert_eq!(WeightedIndex::new(&[1, 2]), WeightedIndex::new(&[1, 2])); + assert_eq!(WeightedIndex::new([1, 2]), WeightedIndex::new([1, 2])); } #[test] diff --git a/src/lib.rs b/src/lib.rs index af51387441a..3e8ab5c7c77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,7 @@ #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] #![cfg_attr(feature = "simd_support", feature(portable_simd))] +#![allow(unexpected_cfgs)] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![allow( clippy::float_cmp, @@ -57,8 +58,10 @@ clippy::nonminimal_bool )] -#[cfg(feature = "alloc")] extern crate alloc; -#[cfg(feature = "std")] extern crate std; +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; #[allow(unused)] macro_rules! trace { ($($x:tt)*) => ( @@ -160,7 +163,9 @@ use crate::distributions::{Distribution, Standard}; )] #[inline] pub fn random() -> T -where Standard: Distribution { +where + Standard: Distribution, +{ thread_rng().random() } diff --git a/src/prelude.rs b/src/prelude.rs index 87613532f78..2605bca91f4 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -18,7 +18,8 @@ //! # let _: f32 = r.random(); //! ``` -#[doc(no_inline)] pub use crate::distributions::Distribution; +#[doc(no_inline)] +pub use crate::distributions::Distribution; #[cfg(feature = "small_rng")] #[doc(no_inline)] pub use crate::rngs::SmallRng; @@ -33,4 +34,5 @@ pub use crate::seq::{IndexedMutRandom, IndexedRandom, IteratorRandom, SliceRando #[doc(no_inline)] #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use crate::{random, thread_rng}; -#[doc(no_inline)] pub use crate::{CryptoRng, Rng, RngCore, SeedableRng}; +#[doc(no_inline)] +pub use crate::{CryptoRng, Rng, RngCore, SeedableRng}; diff --git a/src/rng.rs b/src/rng.rs index 129c141796e..06fc2bb741e 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -89,7 +89,9 @@ pub trait Rng: RngCore { /// [`Standard`]: distributions::Standard #[inline] fn random(&mut self) -> T - where Standard: Distribution { + where + Standard: Distribution, + { Standard.sample(self) } @@ -309,7 +311,9 @@ pub trait Rng: RngCore { note = "Renamed to `random` to avoid conflict with the new `gen` keyword in Rust 2024." )] fn gen(&mut self) -> T - where Standard: Distribution { + where + Standard: Distribution, + { self.random() } } @@ -402,7 +406,8 @@ impl_fill!(u16, u32, u64, usize, u128,); impl_fill!(i8, i16, i32, i64, isize, i128,); impl Fill for [T; N] -where [T]: Fill +where + [T]: Fill, { fn fill(&mut self, rng: &mut R) { <[T] as Fill>::fill(self, rng) @@ -414,7 +419,8 @@ mod test { use super::*; use crate::rngs::mock::StepRng; use crate::test::rng; - #[cfg(feature = "alloc")] use alloc::boxed::Box; + #[cfg(feature = "alloc")] + use alloc::boxed::Box; #[test] fn test_fill_bytes_default() { diff --git a/src/rngs/mock.rs b/src/rngs/mock.rs index e186fb7f062..a01a6bd7b4e 100644 --- a/src/rngs/mock.rs +++ b/src/rngs/mock.rs @@ -10,7 +10,8 @@ use rand_core::{impls, RngCore}; -#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; /// A mock generator yielding very predictable output /// @@ -78,7 +79,8 @@ rand_core::impl_try_rng_from_rng_core!(StepRng); #[cfg(test)] mod tests { - #[cfg(any(feature = "alloc", feature = "serde1"))] use super::StepRng; + #[cfg(any(feature = "alloc", feature = "serde1"))] + use super::StepRng; #[test] #[cfg(feature = "serde1")] diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index d357a715669..0aa6c427d85 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -102,18 +102,22 @@ pub use reseeding::ReseedingRng; pub mod mock; // Public so we don't export `StepRng` directly, making it a bit // more clear it is intended for testing. -#[cfg(feature = "small_rng")] mod small; +#[cfg(feature = "small_rng")] +mod small; #[cfg(all(feature = "small_rng", not(target_pointer_width = "64")))] mod xoshiro128plusplus; #[cfg(all(feature = "small_rng", target_pointer_width = "64"))] mod xoshiro256plusplus; -#[cfg(feature = "std_rng")] mod std; +#[cfg(feature = "std_rng")] +mod std; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub(crate) mod thread; -#[cfg(feature = "small_rng")] pub use self::small::SmallRng; -#[cfg(feature = "std_rng")] pub use self::std::StdRng; +#[cfg(feature = "small_rng")] +pub use self::small::SmallRng; +#[cfg(feature = "std_rng")] +pub use self::std::StdRng; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use self::thread::ThreadRng; diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 08654149670..f9f1a0af676 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -33,7 +33,6 @@ use crate::rngs::ReseedingRng; // `ThreadRng` internally, which is nonsensical anyway. We should also never run // `ThreadRng` in destructors of its implementation, which is also nonsensical. - // Number of generated bytes after which to reseed `ThreadRng`. // According to benchmarks, reseeding has a noticeable impact with thresholds // of 32 kB and less. We choose 64 kB to avoid significant overhead. diff --git a/src/rngs/xoshiro128plusplus.rs b/src/rngs/xoshiro128plusplus.rs index 416ea91a9fa..aa621950164 100644 --- a/src/rngs/xoshiro128plusplus.rs +++ b/src/rngs/xoshiro128plusplus.rs @@ -9,7 +9,8 @@ use rand_core::impls::{fill_bytes_via_next, next_u64_via_u32}; use rand_core::le::read_u32_into; use rand_core::{RngCore, SeedableRng}; -#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; /// A xoshiro128++ random number generator. /// diff --git a/src/rngs/xoshiro256plusplus.rs b/src/rngs/xoshiro256plusplus.rs index 0fdc66df7c8..d6210df6b39 100644 --- a/src/rngs/xoshiro256plusplus.rs +++ b/src/rngs/xoshiro256plusplus.rs @@ -9,7 +9,8 @@ use rand_core::impls::fill_bytes_via_next; use rand_core::le::read_u64_into; use rand_core::{RngCore, SeedableRng}; -#[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; /// A xoshiro256++ random number generator. /// diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 7a97fa8aaf0..4c41c07da44 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -10,7 +10,7 @@ use crate::RngCore; pub(crate) struct CoinFlipper { pub rng: R, - chunk: u32, //TODO(opt): this should depend on RNG word size + chunk: u32, // TODO(opt): this should depend on RNG word size chunk_remaining: u32, } @@ -92,7 +92,7 @@ impl CoinFlipper { // If n * 2^c > `usize::MAX` we always return `true` anyway n = n.saturating_mul(2_usize.pow(c)); } else { - //At least one tail + // At least one tail if c == 1 { // Calculate 2n - d. // We need to use wrapping as 2n might be greater than `usize::MAX` diff --git a/src/seq/index.rs b/src/seq/index.rs index e34b1c2ca62..9ef6bf89a45 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -7,23 +7,30 @@ // except according to those terms. //! Low-level API for sampling indices +use core::{cmp::Ordering, hash::Hash, ops::AddAssign}; -#[cfg(feature = "alloc")] use core::slice; +#[cfg(feature = "alloc")] +use core::slice; -#[cfg(feature = "alloc")] use alloc::vec::{self, Vec}; +#[cfg(feature = "alloc")] +use alloc::vec::{self, Vec}; // BTreeMap is not as fast in tests, but better than nothing. #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::collections::BTreeSet; -#[cfg(feature = "std")] use std::collections::HashSet; +#[cfg(feature = "std")] +use std::collections::HashSet; #[cfg(feature = "std")] use super::WeightError; #[cfg(feature = "alloc")] -use crate::{Rng, distributions::{uniform::SampleUniform, Distribution, Uniform}}; +use crate::{ + distributions::{uniform::SampleUniform, Distribution, Uniform}, + Rng, +}; #[cfg(feature = "serde1")] -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; /// A vector of indices. /// @@ -88,8 +95,8 @@ impl IndexVec { } impl IntoIterator for IndexVec { - type Item = usize; type IntoIter = IndexVecIntoIter; + type Item = usize; /// Convert into an iterator over the indices as a sequence of `usize` values #[inline] @@ -196,7 +203,6 @@ impl Iterator for IndexVecIntoIter { impl ExactSizeIterator for IndexVecIntoIter {} - /// Randomly sample exactly `amount` distinct indices from `0..length`, and /// return them in random order (fully shuffled). /// @@ -221,7 +227,9 @@ impl ExactSizeIterator for IndexVecIntoIter {} /// Panics if `amount > length`. #[track_caller] pub fn sample(rng: &mut R, length: usize, amount: usize) -> IndexVec -where R: Rng + ?Sized { +where + R: Rng + ?Sized, +{ if amount > length { panic!("`amount` of samples must be less than or equal to `length`"); } @@ -276,7 +284,10 @@ where R: Rng + ?Sized { #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn sample_weighted( - rng: &mut R, length: usize, weight: F, amount: usize, + rng: &mut R, + length: usize, + weight: F, + amount: usize, ) -> Result where R: Rng + ?Sized, @@ -293,7 +304,6 @@ where } } - /// Randomly sample exactly `amount` distinct indices from `0..length`, and /// return them in an arbitrary order (there is no guarantee of shuffling or /// ordering). The weights are to be provided by the input function `weights`, @@ -308,7 +318,10 @@ where /// - [`WeightError::InsufficientNonZero`] when fewer than `amount` weights are positive. #[cfg(feature = "std")] fn sample_efraimidis_spirakis( - rng: &mut R, length: N, weight: F, amount: N, + rng: &mut R, + length: N, + weight: F, + amount: N, ) -> Result where R: Rng + ?Sized, @@ -325,23 +338,27 @@ where index: N, key: f64, } + impl PartialOrd for Element { - fn partial_cmp(&self, other: &Self) -> Option { - self.key.partial_cmp(&other.key) + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } + impl Ord for Element { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - // partial_cmp will always produce a value, - // because we check that the weights are not nan - self.partial_cmp(other).unwrap() + fn cmp(&self, other: &Self) -> Ordering { + // partial_cmp will always produce a value, + // because we check that the weights are not nan + self.key.partial_cmp(&other.key).unwrap() } } + impl PartialEq for Element { fn eq(&self, other: &Self) -> bool { self.key == other.key } } + impl Eq for Element {} let mut candidates = Vec::with_capacity(length.as_usize()); @@ -367,8 +384,7 @@ where // keys. Do this by using `select_nth_unstable` to put the elements with // the *smallest* keys at the beginning of the list in `O(n)` time, which // provides equivalent information about the elements with the *greatest* keys. - let (_, mid, greater) - = candidates.select_nth_unstable(avail - amount.as_usize()); + let (_, mid, greater) = candidates.select_nth_unstable(avail - amount.as_usize()); let mut result: Vec = Vec::with_capacity(amount.as_usize()); result.push(mid.index); @@ -385,7 +401,9 @@ where /// /// This implementation uses `O(amount)` memory and `O(amount^2)` time. fn sample_floyd(rng: &mut R, length: u32, amount: u32) -> IndexVec -where R: Rng + ?Sized { +where + R: Rng + ?Sized, +{ // Note that the values returned by `rng.gen_range()` can be // inferred from the returned vector by working backwards from // the last entry. This bijection proves the algorithm fair. @@ -414,7 +432,9 @@ where R: Rng + ?Sized { /// /// Set-up is `O(length)` time and memory and shuffling is `O(amount)` time. fn sample_inplace(rng: &mut R, length: u32, amount: u32) -> IndexVec -where R: Rng + ?Sized { +where + R: Rng + ?Sized, +{ debug_assert!(amount <= length); let mut indices: Vec = Vec::with_capacity(length as usize); indices.extend(0..length); @@ -427,12 +447,12 @@ where R: Rng + ?Sized { IndexVec::from(indices) } -trait UInt: Copy + PartialOrd + Ord + PartialEq + Eq + SampleUniform - + core::hash::Hash + core::ops::AddAssign { +trait UInt: Copy + PartialOrd + Ord + PartialEq + Eq + SampleUniform + Hash + AddAssign { fn zero() -> Self; fn one() -> Self; fn as_usize(self) -> usize; } + impl UInt for u32 { #[inline] fn zero() -> Self { @@ -449,6 +469,7 @@ impl UInt for u32 { self as usize } } + impl UInt for usize { #[inline] fn zero() -> Self { @@ -507,19 +528,23 @@ mod test { #[cfg(feature = "serde1")] fn test_serialization_index_vec() { let some_index_vec = IndexVec::from(vec![254_usize, 234, 2, 1]); - let de_some_index_vec: IndexVec = bincode::deserialize(&bincode::serialize(&some_index_vec).unwrap()).unwrap(); + let de_some_index_vec: IndexVec = + bincode::deserialize(&bincode::serialize(&some_index_vec).unwrap()).unwrap(); match (some_index_vec, de_some_index_vec) { (IndexVec::U32(a), IndexVec::U32(b)) => { assert_eq!(a, b); - }, + } (IndexVec::USize(a), IndexVec::USize(b)) => { assert_eq!(a, b); - }, - _ => {panic!("failed to seralize/deserialize `IndexVec`")} + } + _ => { + panic!("failed to seralize/deserialize `IndexVec`") + } } } - #[cfg(feature = "alloc")] use alloc::vec; + #[cfg(feature = "alloc")] + use alloc::vec; #[test] fn test_sample_boundaries() { @@ -593,7 +618,7 @@ mod test { for &i in &indices { assert!((i as usize) < len); } - }, + } IndexVec::USize(_) => panic!("expected `IndexVec::U32`"), } } @@ -628,11 +653,15 @@ mod test { do_test(300, 80, &[31, 289, 248, 154, 221, 243, 7, 192]); // inplace do_test(300, 180, &[31, 289, 248, 154, 221, 243, 7, 192]); // inplace - do_test(1_000_000, 8, &[ - 103717, 963485, 826422, 509101, 736394, 807035, 5327, 632573, - ]); // floyd - do_test(1_000_000, 180, &[ - 103718, 963490, 826426, 509103, 736396, 807036, 5327, 632573, - ]); // rejection + do_test( + 1_000_000, + 8, + &[103717, 963485, 826422, 509101, 736394, 807035, 5327, 632573], + ); // floyd + do_test( + 1_000_000, + 180, + &[103718, 963490, 826426, 509103, 736396, 807036, 5327, 632573], + ); // rejection } } diff --git a/src/seq/mod.rs b/src/seq/mod.rs index fc1cc993113..1df250d04af 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -44,7 +44,8 @@ use alloc::vec::Vec; #[cfg(feature = "alloc")] use crate::distributions::uniform::{SampleBorrow, SampleUniform}; -#[cfg(feature = "alloc")] use crate::distributions::Weight; +#[cfg(feature = "alloc")] +use crate::distributions::Weight; use crate::Rng; use self::coin_flipper::CoinFlipper; @@ -167,7 +168,9 @@ pub trait IndexedRandom: Index { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted( - &self, rng: &mut R, weight: F, + &self, + rng: &mut R, + weight: F, ) -> Result<&Self::Output, WeightError> where R: Rng + ?Sized, @@ -212,13 +215,15 @@ pub trait IndexedRandom: Index { /// println!("{:?}", choices.choose_multiple_weighted(&mut rng, 2, |item| item.1).unwrap().collect::>()); /// ``` /// [`choose_multiple`]: IndexedRandom::choose_multiple - // // Note: this is feature-gated on std due to usage of f64::powf. // If necessary, we may use alloc+libm as an alternative (see PR #1089). #[cfg(feature = "std")] #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] fn choose_multiple_weighted( - &self, rng: &mut R, amount: usize, weight: F, + &self, + rng: &mut R, + amount: usize, + weight: F, ) -> Result, WeightError> where Self::Output: Sized, @@ -285,7 +290,9 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted_mut( - &mut self, rng: &mut R, weight: F, + &mut self, + rng: &mut R, + weight: F, ) -> Result<&mut Self::Output, WeightError> where R: Rng + ?Sized, @@ -358,7 +365,9 @@ pub trait SliceRandom: IndexedMutRandom { /// /// For slices, complexity is `O(m)` where `m = amount`. fn partial_shuffle( - &mut self, rng: &mut R, amount: usize, + &mut self, + rng: &mut R, + amount: usize, ) -> (&mut [Self::Output], &mut [Self::Output]) where Self::Output: Sized, @@ -624,9 +633,7 @@ impl SliceRandom for [T] { self.partial_shuffle(rng, self.len()); } - fn partial_shuffle( - &mut self, rng: &mut R, amount: usize, - ) -> (&mut [T], &mut [T]) + fn partial_shuffle(&mut self, rng: &mut R, amount: usize) -> (&mut [T], &mut [T]) where R: Rng + ?Sized, { @@ -1294,7 +1301,10 @@ mod test { fn do_test>(iter: I, v: &[u32]) { let mut rng = crate::test::rng(412); let mut buf = [0u32; 8]; - assert_eq!(iter.clone().choose_multiple_fill(&mut rng, &mut buf), v.len()); + assert_eq!( + iter.clone().choose_multiple_fill(&mut rng, &mut buf), + v.len() + ); assert_eq!(&buf[0..v.len()], v); #[cfg(feature = "alloc")] From 890ad8b68bbb10a384a6073b47552a889da2424e Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Fri, 10 May 2024 13:46:26 +0300 Subject: [PATCH 384/443] Rename `doc_cfg` to `docsrs` and use `doc_auto_cfg` (#1450) --- Cargo.toml | 4 ++-- rand_core/Cargo.toml | 4 ++-- rand_core/src/blanket_impls.rs | 4 ---- rand_core/src/impls.rs | 6 +++++- rand_core/src/lib.rs | 5 +---- rand_core/src/os.rs | 1 - rand_distr/Cargo.toml | 2 +- rand_distr/src/binomial.rs | 1 - rand_distr/src/cauchy.rs | 1 - rand_distr/src/dirichlet.rs | 5 ----- rand_distr/src/exponential.rs | 1 - rand_distr/src/frechet.rs | 1 - rand_distr/src/gamma.rs | 4 ---- rand_distr/src/geometric.rs | 1 - rand_distr/src/gumbel.rs | 1 - rand_distr/src/hypergeometric.rs | 1 - rand_distr/src/inverse_gaussian.rs | 1 - rand_distr/src/lib.rs | 9 +-------- rand_distr/src/normal.rs | 1 - rand_distr/src/normal_inverse_gaussian.rs | 1 - rand_distr/src/pareto.rs | 1 - rand_distr/src/pert.rs | 1 - rand_distr/src/poisson.rs | 1 - rand_distr/src/skew_normal.rs | 1 - rand_distr/src/triangular.rs | 1 - rand_distr/src/weibull.rs | 1 - rand_distr/src/weighted_alias.rs | 2 -- rand_distr/src/weighted_tree.rs | 3 +-- rand_distr/src/zipf.rs | 2 -- src/distributions/distribution.rs | 1 - src/distributions/float.rs | 9 +++------ src/distributions/integer.rs | 3 --- src/distributions/mod.rs | 1 - src/distributions/other.rs | 3 --- src/distributions/slice.rs | 1 - src/distributions/uniform.rs | 10 ++-------- src/distributions/weighted_index.rs | 2 -- src/lib.rs | 7 +------ src/rngs/mod.rs | 1 - src/rngs/small.rs | 1 - src/rngs/std.rs | 1 - src/rngs/thread.rs | 8 -------- src/seq/index.rs | 1 - src/seq/mod.rs | 9 --------- 44 files changed, 19 insertions(+), 106 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2ea562a28c..a0805e38b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,9 @@ include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] # To build locally: -# RUSTDOCFLAGS="--cfg doc_cfg -Zunstable-options --generate-link-to-definition" cargo +nightly doc --all --all-features --no-deps --open +# RUSTDOCFLAGS="--cfg docsrs -Zunstable-options --generate-link-to-definition" cargo +nightly doc --all --all-features --no-deps --open all-features = true -rustdoc-args = ["--cfg", "doc_cfg", "-Zunstable-options", "--generate-link-to-definition"] +rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] [package.metadata.playground] features = ["small_rng", "serde1"] diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 7c4118f632d..0053b40245f 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -17,9 +17,9 @@ rust-version = "1.61" [package.metadata.docs.rs] # To build locally: -# RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --all-features --no-deps --open +# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --open all-features = true -rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"] +rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] [package.metadata.playground] all-features = true diff --git a/rand_core/src/blanket_impls.rs b/rand_core/src/blanket_impls.rs index e3b54f894d1..754382f5179 100644 --- a/rand_core/src/blanket_impls.rs +++ b/rand_core/src/blanket_impls.rs @@ -44,7 +44,6 @@ impl<'a, R: TryRngCore + ?Sized> TryRngCore for &'a mut R { impl<'a, R: TryCryptoRng + ?Sized> TryCryptoRng for &'a mut R {} #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl RngCore for Box { #[inline(always)] fn next_u32(&mut self) -> u32 { @@ -63,11 +62,9 @@ impl RngCore for Box { } #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl CryptoRng for Box {} #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl TryRngCore for Box { type Error = R::Error; @@ -88,5 +85,4 @@ impl TryRngCore for Box { } #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl TryCryptoRng for Box {} diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index ff07d7834c1..34230e15822 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -160,7 +160,7 @@ pub fn next_u64_via_fill(rng: &mut R) -> u64 { u64::from_le_bytes(buf) } -/// Implement [`TryRngCore`] for a type implementing [`RngCore`]. +/// Implement [`TryRngCore`][crate::TryRngCore] for a type implementing [`RngCore`]. /// /// Ideally, `rand_core` would define blanket impls for this, but they conflict with blanket impls /// for `&mut R` and `Box`, so until specialziation is stabilized, implementer crates @@ -195,6 +195,10 @@ macro_rules! impl_try_rng_from_rng_core { /// Ideally, `rand_core` would define blanket impls for this, but they conflict with blanket impls /// for `&mut R` and `Box`, so until specialziation is stabilized, implementer crates /// have to implement `TryRngCore` and `TryCryptoRng` directly. +/// +/// [`TryCryptoRng`]: crate::TryCryptoRng +/// [`TryRngCore`]: crate::TryRngCore +/// [`CryptoRng`]: crate::CryptoRng #[macro_export] macro_rules! impl_try_crypto_rng_from_crypto_rng { ($t:ty) => { diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 48c9c528dfc..78b7d655458 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -32,8 +32,7 @@ #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![doc(test(attr(allow(unused_variables), deny(warnings))))] -#![allow(unexpected_cfgs)] -#![cfg_attr(doc_cfg, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![no_std] #[cfg(feature = "alloc")] @@ -446,7 +445,6 @@ pub trait SeedableRng: Sized { /// [`getrandom`]: https://docs.rs/getrandom /// [`try_from_os_rng`]: SeedableRng::try_from_os_rng #[cfg(feature = "getrandom")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] fn from_os_rng() -> Self { match Self::try_from_os_rng() { Ok(res) => res, @@ -463,7 +461,6 @@ pub trait SeedableRng: Sized { /// /// [`getrandom`]: https://docs.rs/getrandom #[cfg(feature = "getrandom")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] fn try_from_os_rng() -> Result { let mut seed = Self::Seed::default(); getrandom::getrandom(seed.as_mut())?; diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index 7d0a8ce0adb..86ae462e5a3 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -44,7 +44,6 @@ use getrandom::getrandom; /// ``` /// /// [getrandom]: https://crates.io/crates/getrandom -#[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] #[derive(Clone, Copy, Debug, Default)] pub struct OsRng; diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 2d23e8a9fbf..5ca2f3d93b0 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.61" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] -rustdoc-args = ["--generate-link-to-definition"] +rustdoc-args = ["--cfg docsrs", "--generate-link-to-definition"] [features] default = ["std"] diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index a9e6a708427..3623561e78b 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -58,7 +58,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Binomial { diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 5a445ff849e..268b5492a98 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -58,7 +58,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Cauchy diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index a7e11482a34..ca192c01948 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -31,7 +31,6 @@ where } /// Error type returned from `DirchletFromGamma::new`. -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum DirichletFromGammaError { /// Gamma::new(a, 1) failed. @@ -104,7 +103,6 @@ where } /// Error type returned from `DirchletFromBeta::new`. -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum DirichletFromBetaError { /// Beta::new(a, b) failed. @@ -203,7 +201,6 @@ where /// let samples = dirichlet.sample(&mut rand::thread_rng()); /// println!("{:?} is from a Dirichlet([1.0, 2.0, 3.0]) distribution", samples); /// ``` -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[cfg_attr(feature = "serde_with", serde_as)] #[derive(Clone, Debug, PartialEq)] pub struct Dirichlet @@ -217,7 +214,6 @@ where } /// Error type returned from `Dirchlet::new`. -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `alpha.len() < 2`. @@ -257,7 +253,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Dirichlet diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index 1fa56a95b15..88f60d236c9 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -118,7 +118,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Exp diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index 781b7af1648..22d7b62e848 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -61,7 +61,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Frechet diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index fbafd26824c..8b3a39205a6 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -88,7 +88,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} #[derive(Clone, Copy, Debug, PartialEq)] @@ -311,7 +310,6 @@ impl fmt::Display for ChiSquaredError { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for ChiSquaredError {} #[derive(Clone, Copy, Debug, PartialEq)] @@ -421,7 +419,6 @@ impl fmt::Display for FisherFError { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for FisherFError {} impl FisherF @@ -592,7 +589,6 @@ impl fmt::Display for BetaError { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for BetaError {} impl Beta diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 0f14d4227d9..e4bef5cf52f 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -53,7 +53,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Geometric { diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 4a83658692a..3b2e79b9f35 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -57,7 +57,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Gumbel diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 84fde5437d8..f9ffda25725 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -87,7 +87,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} // evaluate fact(numerator.0)*fact(numerator.1) / fact(denominator.0)*fact(denominator.1) diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 6518ee9e958..39e53eb0f74 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -22,7 +22,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} /// The [inverse Gaussian distribution](https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution) diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index ea57dd0f742..0d9c6c01a70 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -21,8 +21,7 @@ )] #![allow(clippy::neg_cmp_op_on_partial_ord)] // suggested fix too verbose #![no_std] -#![allow(unexpected_cfgs)] -#![cfg_attr(doc_cfg, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] //! Generating random samples from probability distributions. //! @@ -102,7 +101,6 @@ pub use rand::distributions::{ pub use self::binomial::{Binomial, Error as BinomialError}; pub use self::cauchy::{Cauchy, Error as CauchyError}; #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use self::dirichlet::{Dirichlet, Error as DirichletError}; pub use self::exponential::{Error as ExpError, Exp, Exp1}; pub use self::frechet::{Error as FrechetError, Frechet}; @@ -130,13 +128,10 @@ pub use self::unit_sphere::UnitSphere; pub use self::weibull::{Error as WeibullError, Weibull}; pub use self::zipf::{Zeta, ZetaError, Zipf, ZipfError}; #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use rand::distributions::{WeightError, WeightedIndex}; #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use weighted_alias::WeightedAliasIndex; #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use weighted_tree::WeightedTreeIndex; pub use num_traits; @@ -193,10 +188,8 @@ mod test { } #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod weighted_alias; #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod weighted_tree; mod binomial; diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 8ef231b0f32..1d427514eca 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -142,7 +142,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Normal diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index b7988f40bb3..18540a2bfcf 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -26,7 +26,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} /// The [normal-inverse Gaussian distribution](https://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index 952afec4bc5..8c785e5f292 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -53,7 +53,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Pareto diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index 48114d97be2..bc028e6a4aa 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -66,7 +66,6 @@ impl fmt::Display for PertError { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for PertError {} impl Pert diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 5de3113de00..d50769da191 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -62,7 +62,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Poisson diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index ad7dd2b5635..908484f3737 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -73,7 +73,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl SkewNormal diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index 083725f78c7..795a5473e76 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -64,7 +64,6 @@ impl fmt::Display for TriangularError { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for TriangularError {} impl Triangular diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index 2fba2a87079..4d094b1f0d1 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -53,7 +53,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} impl Weibull diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index beb31f0733b..e8c455dcb1b 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -65,7 +65,6 @@ use serde::{Deserialize, Serialize}; /// [`Vec`]: Vec /// [`Uniform::sample`]: Distribution::sample /// [`Uniform::sample`]: Distribution::sample -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde1", @@ -279,7 +278,6 @@ where /// Trait that must be implemented for weights, that are used with /// [`WeightedAliasIndex`]. Currently no guarantees on the correctness of /// [`WeightedAliasIndex`] are given for custom implementations of this trait. -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub trait AliasableWeight: Sized + Copy diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index 67ce48bad80..f3463bcd960 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -77,14 +77,13 @@ use serde::{Deserialize, Serialize}; /// ``` /// /// [`WeightedTreeIndex`]: WeightedTreeIndex -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde1", serde(bound(serialize = "W: Serialize, W::Sampler: Serialize")) )] #[cfg_attr( - feature = "serde1 ", + feature = "serde1", serde(bound(deserialize = "W: Deserialize<'de>, W::Sampler: Deserialize<'de>")) )] #[derive(Clone, Default, Debug, PartialEq)] diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 7b207a46a78..88848600e82 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -73,7 +73,6 @@ impl fmt::Display for ZetaError { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for ZetaError {} impl Zeta @@ -181,7 +180,6 @@ impl fmt::Display for ZipfError { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for ZipfError {} impl Zipf diff --git a/src/distributions/distribution.rs b/src/distributions/distribution.rs index 2bd6a6a4044..d545eeea457 100644 --- a/src/distributions/distribution.rs +++ b/src/distributions/distribution.rs @@ -186,7 +186,6 @@ where /// Sampling a `String` of random characters is not quite the same as collecting /// a sequence of chars. This trait contains some helpers. #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub trait DistString { /// Append `len` random chars to `string` fn append_string(&self, rng: &mut R, string: &mut String, len: usize); diff --git a/src/distributions/float.rs b/src/distributions/float.rs index 84022685817..427385e50a2 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -104,8 +104,7 @@ macro_rules! float_impls { } } - $(#[cfg($meta)] - #[cfg_attr(doc_cfg, doc(cfg($meta)))])? + $(#[cfg($meta)])? impl Distribution<$ty> for Standard { fn sample(&self, rng: &mut R) -> $ty { // Multiply-based method; 24/53 random bits; [0, 1) interval. @@ -121,8 +120,7 @@ macro_rules! float_impls { } } - $(#[cfg($meta)] - #[cfg_attr(doc_cfg, doc(cfg($meta)))])? + $(#[cfg($meta)])? impl Distribution<$ty> for OpenClosed01 { fn sample(&self, rng: &mut R) -> $ty { // Multiply-based method; 24/53 random bits; (0, 1] interval. @@ -139,8 +137,7 @@ macro_rules! float_impls { } } - $(#[cfg($meta)] - #[cfg_attr(doc_cfg, doc(cfg($meta)))])? + $(#[cfg($meta)])? impl Distribution<$ty> for Open01 { fn sample(&self, rng: &mut R) -> $ty { // Transmute-based method; 23/52 random bits; (0, 1) interval. diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index 60bcbf9565f..66258dcbd5c 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -127,7 +127,6 @@ impl_nzint!(NonZeroIsize, NonZeroIsize::new); macro_rules! x86_intrinsic_impl { ($meta:meta, $($intrinsic:ident),+) => {$( #[cfg($meta)] - #[cfg_attr(doc_cfg, doc(cfg($meta)))] impl Distribution<$intrinsic> for Standard { #[inline] fn sample(&self, rng: &mut R) -> $intrinsic { @@ -149,8 +148,6 @@ macro_rules! simd_impl { /// /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[cfg(feature = "simd_support")] - // TODO: as doc_cfg/doc_auto_cfg mature ensure they catch this - #[cfg_attr(doc_cfg, doc(cfg(feature = "simd_support")))] impl Distribution> for Standard where LaneCount: SupportedLaneCount, diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index e5973297dd9..b31ebe14f18 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -111,7 +111,6 @@ pub mod uniform; pub use self::bernoulli::{Bernoulli, BernoulliError}; #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub use self::distribution::DistString; pub use self::distribution::{DistIter, DistMap, Distribution}; pub use self::float::{Open01, OpenClosed01}; diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 15ccf30a8c9..21289ca94d0 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -96,7 +96,6 @@ impl Distribution for Standard { /// Note: the `String` is potentially left with excess capacity; optionally the /// user may call `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl DistString for Standard { fn append_string(&self, rng: &mut R, s: &mut String, len: usize) { // A char is encoded with at most four bytes, thus this reservation is @@ -127,7 +126,6 @@ impl Distribution for Alphanumeric { } #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl DistString for Alphanumeric { fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { unsafe { @@ -175,7 +173,6 @@ impl Distribution for Standard { /// [`_mm_blendv_epi8`]: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8&ig_expand=514/ /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[cfg(feature = "simd_support")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "simd_support")))] impl Distribution> for Standard where T: MaskElement + Default, diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 88cff8897bb..10f830b7bb3 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -129,7 +129,6 @@ impl std::error::Error for EmptySlice {} /// Note: the `String` is potentially left with excess capacity; optionally the /// user may call `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl<'a> super::DistString for Slice<'a, char> { fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { // Get the max char length to minimize extra space. diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index fde8d6dbbec..34a6b252f4d 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -140,7 +140,6 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] impl std::error::Error for Error {} #[cfg(feature = "serde1")] @@ -676,7 +675,6 @@ macro_rules! uniform_simd_int_impl { // implement it manually. #[cfg(feature = "simd_support")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "simd_support")))] impl SampleUniform for Simd<$ty, LANES> where LaneCount: SupportedLaneCount, @@ -688,7 +686,6 @@ macro_rules! uniform_simd_int_impl { } #[cfg(feature = "simd_support")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "simd_support")))] impl UniformSampler for UniformInt> where LaneCount: SupportedLaneCount, @@ -864,7 +861,6 @@ impl UniformSampler for UniformChar { /// includes non ascii chars; optionally the user may call /// `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl super::DistString for Uniform { fn append_string( &self, @@ -912,14 +908,12 @@ pub struct UniformFloat { macro_rules! uniform_float_impl { ($($meta:meta)?, $ty:ty, $uty:ident, $f_scalar:ident, $u_scalar:ident, $bits_to_discard:expr) => { - $(#[cfg($meta)] - #[cfg_attr(doc_cfg, doc(cfg($meta)))])? + $(#[cfg($meta)])? impl SampleUniform for $ty { type Sampler = UniformFloat<$ty>; } - $(#[cfg($meta)] - #[cfg_attr(doc_cfg, doc(cfg($meta)))])? + $(#[cfg($meta)])? impl UniformSampler for UniformFloat<$ty> { type X = $ty; diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index a021376639f..ec3bbade050 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -82,7 +82,6 @@ use serde::{Deserialize, Serialize}; /// [`RngCore`]: crate::RngCore #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub struct WeightedIndex { cumulative_weights: Vec, total_weight: X, @@ -702,7 +701,6 @@ mod test { } /// Errors returned by weighted distributions -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WeightError { /// The input weight sequence is empty, too long, or wrongly ordered diff --git a/src/lib.rs b/src/lib.rs index 3e8ab5c7c77..8b43f0320f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,8 +50,7 @@ #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] #![cfg_attr(feature = "simd_support", feature(portable_simd))] -#![allow(unexpected_cfgs)] -#![cfg_attr(doc_cfg, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![allow( clippy::float_cmp, clippy::neg_cmp_op_on_partial_ord, @@ -157,10 +156,6 @@ use crate::distributions::{Distribution, Standard}; /// [`Standard`]: distributions::Standard /// [`ThreadRng`]: rngs::ThreadRng #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] -#[cfg_attr( - doc_cfg, - doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))) -)] #[inline] pub fn random() -> T where diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index 0aa6c427d85..413d61bfdf7 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -121,6 +121,5 @@ pub use self::std::StdRng; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] pub use self::thread::ThreadRng; -#[cfg_attr(doc_cfg, doc(cfg(feature = "getrandom")))] #[cfg(feature = "getrandom")] pub use rand_core::OsRng; diff --git a/src/rngs/small.rs b/src/rngs/small.rs index 12276885f69..b53e34430e0 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -38,7 +38,6 @@ type Rng = super::xoshiro128plusplus::Xoshiro128PlusPlus; /// [`StdRng`]: crate::rngs::StdRng /// [rand_chacha]: https://crates.io/crates/rand_chacha /// [rand_xoshiro]: https://crates.io/crates/rand_xoshiro -#[cfg_attr(doc_cfg, doc(cfg(feature = "small_rng")))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct SmallRng(Rng); diff --git a/src/rngs/std.rs b/src/rngs/std.rs index 34a6fa8cdf6..57b9bfeffde 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -30,7 +30,6 @@ use rand_chacha::ChaCha12Rng as Rng; /// /// [rand_chacha]: https://crates.io/crates/rand_chacha /// [rand issue]: https://github.com/rust-random/rand/issues/932 -#[cfg_attr(doc_cfg, doc(cfg(feature = "std_rng")))] #[derive(Clone, Debug, PartialEq, Eq)] pub struct StdRng(Rng); diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index f9f1a0af676..161b5d6efcb 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -76,10 +76,6 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// /// [`ReseedingRng`]: crate::rngs::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng -#[cfg_attr( - doc_cfg, - doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))) -)] #[derive(Clone)] pub struct ThreadRng { // Rc is explicitly !Send and !Sync @@ -135,10 +131,6 @@ thread_local!( /// println!("A simulated die roll: {}", rng.gen_range(1..=6)); /// # } /// ``` -#[cfg_attr( - doc_cfg, - doc(cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))) -)] pub fn thread_rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng } diff --git a/src/seq/index.rs b/src/seq/index.rs index 9ef6bf89a45..42b810f068c 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -282,7 +282,6 @@ where /// /// This implementation uses `O(length + amount)` space and `O(length)` time. #[cfg(feature = "std")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] pub fn sample_weighted( rng: &mut R, length: usize, diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 1df250d04af..d8268e9a981 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -28,7 +28,6 @@ mod coin_flipper; #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod index; mod increasing_uniform; @@ -122,7 +121,6 @@ pub trait IndexedRandom: Index { /// } /// ``` #[cfg(feature = "alloc")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter where Self::Output: Sized, @@ -166,7 +164,6 @@ pub trait IndexedRandom: Index { /// [`choose_weighted_mut`]: IndexedMutRandom::choose_weighted_mut /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex #[cfg(feature = "alloc")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted( &self, rng: &mut R, @@ -218,7 +215,6 @@ pub trait IndexedRandom: Index { // Note: this is feature-gated on std due to usage of f64::powf. // If necessary, we may use alloc+libm as an alternative (see PR #1089). #[cfg(feature = "std")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))] fn choose_multiple_weighted( &self, rng: &mut R, @@ -288,7 +284,6 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { /// [`choose_weighted`]: IndexedRandom::choose_weighted /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex #[cfg(feature = "alloc")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_weighted_mut( &mut self, rng: &mut R, @@ -585,7 +580,6 @@ pub trait IteratorRandom: Iterator + Sized { /// Complexity is `O(n)` where `n` is the length of the iterator. /// For slices, prefer [`IndexedRandom::choose_multiple`]. #[cfg(feature = "alloc")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(mut self, rng: &mut R, amount: usize) -> Vec where R: Rng + ?Sized, @@ -672,7 +666,6 @@ impl IteratorRandom for I where I: Iterator + Sized {} /// This struct is created by /// [`IndexedRandom::choose_multiple`](trait.IndexedRandom.html#tymethod.choose_multiple). #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] #[derive(Debug)] pub struct SliceChooseIter<'a, S: ?Sized + 'a, T: 'a> { slice: &'a S, @@ -681,7 +674,6 @@ pub struct SliceChooseIter<'a, S: ?Sized + 'a, T: 'a> { } #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl<'a, S: Index + ?Sized + 'a, T: 'a> Iterator for SliceChooseIter<'a, S, T> { type Item = &'a T; @@ -696,7 +688,6 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> Iterator for SliceCho } #[cfg(feature = "alloc")] -#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator for SliceChooseIter<'a, S, T> { From 10a1fa4d22d2145cc1f12eaccb5d570e89a9b0f7 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Sat, 11 May 2024 13:42:15 +0300 Subject: [PATCH 385/443] Tweak docs CI job (#1451) --- .github/workflows/test.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index de886e48fb2..c8d1892554f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,16 +13,24 @@ jobs: check-doc: name: Check doc runs-on: ubuntu-latest + env: + RUSTDOCFLAGS: "-Dwarnings --cfg docsrs -Zunstable-options --generate-link-to-definition" steps: - uses: actions/checkout@v4 - name: Install toolchain - uses: dtolnay/rust-toolchain@nightly - - run: cargo install cargo-deadlinks - - name: doc (rand) - env: - RUSTDOCFLAGS: --cfg doc_cfg - # --all builds all crates, but with default features for other crates (okay in this case) - run: cargo deadlinks --ignore-fragments -- --all --features nightly,serde1,getrandom,small_rng + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - name: rand + run: cargo doc --all-features --no-deps + - name: rand_core + run: cargo doc --all-features --package rand_core --no-deps + - name: rand_distr + run: cargo doc --all-features --package rand_distr --no-deps + - name: rand_chacha + run: cargo doc --all-features --package rand_chacha --no-deps + - name: rand_pcg + run: cargo doc --all-features --package rand_pcg --no-deps test: runs-on: ${{ matrix.os }} From defeb0cea3fad7c8790686eedd4a35205a401d16 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 23 May 2024 07:21:07 +0100 Subject: [PATCH 386/443] Revise crate doc for rand_pcg, rand_chacha (#1454) * Pub-reexport rand_core from rand_pcg * Add getrandom feature to rand_pcg, rand_chacha --- rand_chacha/Cargo.toml | 4 ++- rand_chacha/src/lib.rs | 71 +++++++++++++++++++++++++++++++++++++++++- rand_pcg/Cargo.toml | 3 ++ rand_pcg/src/lib.rs | 70 ++++++++++++++++++++++++++++++----------- 4 files changed, 127 insertions(+), 21 deletions(-) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 0162599ce74..981cb7a6a15 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -16,6 +16,7 @@ edition = "2021" rust-version = "1.61" [package.metadata.docs.rs] +all-features = true rustdoc-args = ["--generate-link-to-definition"] [dependencies] @@ -26,9 +27,10 @@ serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] # Only to test serde1 serde_json = "1.0" +rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["getrandom"] } [features] default = ["std"] +getrandom = ["rand_core/getrandom"] std = ["ppv-lite86/std", "rand_core/std"] -simd = [] # deprecated serde1 = ["serde"] diff --git a/rand_chacha/src/lib.rs b/rand_chacha/src/lib.rs index f4b526b8f64..ba72b1b5b60 100644 --- a/rand_chacha/src/lib.rs +++ b/rand_chacha/src/lib.rs @@ -6,7 +6,76 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The ChaCha random number generator. +//! The ChaCha random number generators. +//! +//! These are native Rust implementations of RNGs derived from the +//! [ChaCha stream ciphers] by D J Bernstein. +//! +//! ## Generators +//! +//! This crate provides 8-, 12- and 20-round variants of generators via a "core" +//! implementation (of [`BlockRngCore`]), each with an associated "RNG" type +//! (implementing [`RngCore`]). +//! +//! These generators are all deterministic and portable (see [Reproducibility] +//! in the book), with testing against reference vectors. +//! +//! ## Cryptographic (secure) usage +//! +//! Where secure unpredictable generators are required, it is suggested to use +//! [`ChaCha12Rng`] or [`ChaCha20Rng`] and to seed via +//! [`SeedableRng::from_os_rng`]. +//! +//! See also the [Security] chapter in the rand book. The crate is provided +//! "as is", without any form of guarantee, and without a security audit. +//! +//! ## Seeding (construction) +//! +//! Generators implement the [`SeedableRng`] trait. Any method may be used, +//! but note that `seed_from_u64` is not suitable for usage where security is +//! important. Some suggestions: +//! +//! 1. With a fresh seed, **direct from the OS** (implies a syscall): +//! ``` +//! # use {rand_core::SeedableRng, rand_chacha::ChaCha12Rng}; +//! let rng = ChaCha12Rng::from_os_rng(); +//! # let _: ChaCha12Rng = rng; +//! ``` +//! 2. **From a master generator.** This could be [`rand::thread_rng`] +//! (effectively a fresh seed without the need for a syscall on each usage) +//! or a deterministic generator such as [`ChaCha20Rng`]. +//! Beware that should a weak master generator be used, correlations may be +//! detectable between the outputs of its child generators. +//! +//! See also [Seeding RNGs] in the book. +//! +//! ## Generation +//! +//! Generators implement [`RngCore`], whose methods may be used directly to +//! generate unbounded integer or byte values. +//! ``` +//! use rand_core::{SeedableRng, RngCore}; +//! use rand_chacha::ChaCha12Rng; +//! +//! let mut rng = ChaCha12Rng::from_seed(Default::default()); +//! let x = rng.next_u64(); +//! assert_eq!(x, 0x53f955076a9af49b); +//! ``` +//! +//! It is often more convenient to use the [`rand::Rng`] trait, which provides +//! further functionality. See also the [Random Values] chapter in the book. +//! +//! [ChaCha stream ciphers]: https://cr.yp.to/chacha.html +//! [Reproducibility]: https://rust-random.github.io/book/crate-reprod.html +//! [Seeding RNGs]: https://rust-random.github.io/book/guide-seeding.html +//! [Security]: https://rust-random.github.io/book/guide-rngs.html#security +//! [Random Values]: https://rust-random.github.io/book/guide-values.html +//! [`BlockRngCore`]: rand_core::block::BlockRngCore +//! [`RngCore`]: rand_core::RngCore +//! [`SeedableRng`]: rand_core::SeedableRng +//! [`SeedableRng::from_os_rng`]: rand_core::SeedableRng::from_os_rng +//! [`rand::thread_rng`]: https://docs.rs/rand/latest/rand/fn.thread_rng.html +//! [`rand::Rng`]: https://docs.rs/rand/latest/rand/trait.Rng.html #![doc( html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index e80e16b086a..30c3026fef0 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -16,10 +16,12 @@ edition = "2021" rust-version = "1.61" [package.metadata.docs.rs] +all-features = true rustdoc-args = ["--generate-link-to-definition"] [features] serde1 = ["serde"] +getrandom = ["rand_core/getrandom"] [dependencies] rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1" } @@ -30,3 +32,4 @@ serde = { version = "1", features = ["derive"], optional = true } # deps yet, see: https://github.com/rust-lang/cargo/issues/1596 # Versions prior to 1.1.4 had incorrect minimal dependencies. bincode = { version = "1.1.4" } +rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["getrandom"] } diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index 72e68586839..7ef5dea22bc 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -8,48 +8,78 @@ //! The PCG random number generators. //! -//! This is a native Rust implementation of a small selection of PCG generators. +//! This is a native Rust implementation of a small selection of [PCG generators]. //! The primary goal of this crate is simple, minimal, well-tested code; in //! other words it is explicitly not a goal to re-implement all of PCG. //! +//! ## Generators +//! //! This crate provides: //! -//! - `Pcg32` aka `Lcg64Xsh32`, officially known as `pcg32`, a general +//! - [`Pcg32`] aka [`Lcg64Xsh32`], officially known as `pcg32`, a general //! purpose RNG. This is a good choice on both 32-bit and 64-bit CPUs //! (for 32-bit output). -//! - `Pcg64` aka `Lcg128Xsl64`, officially known as `pcg64`, a general +//! - [`Pcg64`] aka [`Lcg128Xsl64`], officially known as `pcg64`, a general //! purpose RNG. This is a good choice on 64-bit CPUs. -//! - `Pcg64Mcg` aka `Mcg128Xsl64`, officially known as `pcg64_fast`, +//! - [`Pcg64Mcg`] aka [`Mcg128Xsl64`], officially known as `pcg64_fast`, //! a general purpose RNG using 128-bit multiplications. This has poor //! performance on 32-bit CPUs but is a good choice on 64-bit CPUs for //! both 32-bit and 64-bit output. //! -//! Both of these use 16 bytes of state and 128-bit seeds, and are considered -//! value-stable (i.e. any change affecting the output given a fixed seed would -//! be considered a breaking change to the crate). +//! These generators are all deterministic and portable (see [Reproducibility] +//! in the book), with testing against reference vectors. +//! +//! ## Seeding (construction) +//! +//! Generators implement the [`SeedableRng`] trait. All methods are suitable for +//! seeding. Some suggestions: +//! +//! 1. Seed **from an integer** via `seed_from_u64`. This uses a hash function +//! internally to yield a (typically) good seed from any input. +//! ``` +//! # use {rand_core::SeedableRng, rand_pcg::Pcg64Mcg}; +//! let rng = Pcg64Mcg::seed_from_u64(1); +//! # let _: Pcg64Mcg = rng; +//! ``` +//! 2. With a fresh seed, **direct from the OS** (implies a syscall): +//! ``` +//! # use {rand_core::SeedableRng, rand_pcg::Pcg64Mcg}; +//! let rng = Pcg64Mcg::from_os_rng(); +//! # let _: Pcg64Mcg = rng; +//! ``` +//! 3. **From a master generator.** This could be [`rand::thread_rng`] +//! (effectively a fresh seed without the need for a syscall on each usage) +//! or a deterministic generator such as [`rand_chacha::ChaCha8Rng`]. +//! Beware that should a weak master generator be used, correlations may be +//! detectable between the outputs of its child generators. //! -//! # Example +//! See also [Seeding RNGs] in the book. //! -//! To initialize a generator, use the [`SeedableRng`][rand_core::SeedableRng] trait: +//! ## Generation //! +//! Generators implement [`RngCore`], whose methods may be used directly to +//! generate unbounded integer or byte values. //! ``` //! use rand_core::{SeedableRng, RngCore}; //! use rand_pcg::Pcg64Mcg; //! //! let mut rng = Pcg64Mcg::seed_from_u64(0); -//! let x: u32 = rng.next_u32(); +//! let x = rng.next_u64(); +//! assert_eq!(x, 0x5603f242407deca2); //! ``` //! -//! The functionality of this crate is implemented using traits from the `rand_core` crate, but you may use the `rand` -//! crate for further functionality to initialize the generator from various sources and to generate random values: +//! It is often more convenient to use the [`rand::Rng`] trait, which provides +//! further functionality. See also the [Random Values] chapter in the book. //! -//! ```ignore -//! use rand::{Rng, SeedableRng}; -//! use rand_pcg::Pcg64Mcg; -//! -//! let mut rng = Pcg64Mcg::from_os_rng(); -//! let x: f64 = rng.gen(); -//! ``` +//! [PCG generators]: https://www.pcg-random.org/ +//! [Reproducibility]: https://rust-random.github.io/book/crate-reprod.html +//! [Seeding RNGs]: https://rust-random.github.io/book/guide-seeding.html +//! [Random Values]: https://rust-random.github.io/book/guide-values.html +//! [`RngCore`]: rand_core::RngCore +//! [`SeedableRng`]: rand_core::SeedableRng +//! [`rand::thread_rng`]: https://docs.rs/rand/latest/rand/fn.thread_rng.html +//! [`rand::Rng`]: https://docs.rs/rand/latest/rand/trait.Rng.html +//! [`rand_chacha::ChaCha8Rng`]: https://docs.rs/rand_chacha/latest/rand_chacha/struct.ChaCha8Rng.html #![doc( html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", @@ -65,6 +95,8 @@ mod pcg128; mod pcg128cm; mod pcg64; +pub use rand_core; + pub use self::pcg128::{Lcg128Xsl64, Mcg128Xsl64, Pcg64, Pcg64Mcg}; pub use self::pcg128cm::{Lcg128CmDxsm64, Pcg64Dxsm}; pub use self::pcg64::{Lcg64Xsh32, Pcg32}; From ba7f5155d3c2650116d35d3a456f694d2418b2ea Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 23 May 2024 14:12:08 +0100 Subject: [PATCH 387/443] Revise RNG docs; enable small_rng by default (#1455) --- CHANGELOG.md | 1 + Cargo.toml | 2 +- rand_distr/tests/pdf.rs | 2 +- src/rngs/mod.rs | 108 +++++++++++++++--------------------- src/rngs/small.rs | 119 +++++++++++++++++++++++----------------- src/rngs/std.rs | 43 ++++++++++++--- 6 files changed, 153 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 583f162a78a..ae5f29f8411 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) - Move all benchmarks to new `benches` crate (#1439) - Annotate panicking methods with `#[track_caller]` (#1442, #1447) +- Enable feature `small_rng` by default (#1455) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/Cargo.toml b/Cargo.toml index a0805e38b50..740f799a56a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ features = ["small_rng", "serde1"] [features] # Meta-features: -default = ["std", "std_rng", "getrandom"] +default = ["std", "std_rng", "getrandom", "small_rng"] nightly = [] # some additions requiring nightly Rust serde1 = ["serde", "rand_core/serde1"] diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs index 47b00ef7391..1bbbd32d55b 100644 --- a/rand_distr/tests/pdf.rs +++ b/rand_distr/tests/pdf.rs @@ -9,7 +9,7 @@ #![allow(clippy::float_cmp)] use average::Histogram; -use rand::Rng; +use rand::{Rng, SeedableRng}; use rand_distr::{Normal, SkewNormal}; const HIST_LEN: usize = 100; diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index 413d61bfdf7..afd9246d4ab 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -8,82 +8,61 @@ //! Random number generators and adapters //! -//! ## Background: Random number generators (RNGs) +//! This crate provides a small selection of non-[portable] generators. +//! See also [Types of generators] and [Our RNGs] in the book. //! -//! Computers cannot produce random numbers from nowhere. We classify -//! random number generators as follows: +//! ## Generators //! -//! - "True" random number generators (TRNGs) use hard-to-predict data sources -//! (e.g. the high-resolution parts of event timings and sensor jitter) to -//! harvest random bit-sequences, apply algorithms to remove bias and -//! estimate available entropy, then combine these bits into a byte-sequence -//! or an entropy pool. This job is usually done by the operating system or -//! a hardware generator (HRNG). -//! - "Pseudo"-random number generators (PRNGs) use algorithms to transform a -//! seed into a sequence of pseudo-random numbers. These generators can be -//! fast and produce well-distributed unpredictable random numbers (or not). -//! They are usually deterministic: given algorithm and seed, the output -//! sequence can be reproduced. They have finite period and eventually loop; -//! with many algorithms this period is fixed and can be proven sufficiently -//! long, while others are chaotic and the period depends on the seed. -//! - "Cryptographically secure" pseudo-random number generators (CSPRNGs) -//! are the sub-set of PRNGs which are secure. Security of the generator -//! relies both on hiding the internal state and using a strong algorithm. +//! This crate provides a small selection of non-[portable] random number generators: //! -//! ## Traits and functionality -//! -//! All RNGs implement the [`RngCore`] trait, as a consequence of which the -//! [`Rng`] extension trait is automatically implemented. Secure RNGs may -//! additionally implement the [`CryptoRng`] trait. -//! -//! All PRNGs require a seed to produce their random number sequence. The -//! [`SeedableRng`] trait provides three ways of constructing PRNGs: -//! -//! - `from_seed` accepts a type specific to the PRNG -//! - `from_rng` allows a PRNG to be seeded from any other RNG -//! - `seed_from_u64` allows any PRNG to be seeded from a `u64` insecurely -//! - `from_os_rng` securely seeds a PRNG from system randomness source -//! -//! Use the [`rand_core`] crate when implementing your own RNGs. -//! -//! ## Our generators -//! -//! This crate provides several random number generators: -//! -//! - [`OsRng`] is an interface to the operating system's random number -//! source. Typically the operating system uses a CSPRNG with entropy -//! provided by a TRNG and some type of on-going re-seeding. +//! - [`OsRng`] is a stateless interface over the operating system's random number +//! source. This is typically secure with some form of periodic re-seeding. //! - [`ThreadRng`], provided by the [`thread_rng`] function, is a handle to a -//! thread-local CSPRNG with periodic seeding from [`OsRng`]. Because this +//! thread-local generator with periodic seeding from [`OsRng`]. Because this //! is local, it is typically much faster than [`OsRng`]. It should be -//! secure, though the paranoid may prefer [`OsRng`]. +//! secure, but see documentation on [`ThreadRng`]. //! - [`StdRng`] is a CSPRNG chosen for good performance and trust of security //! (based on reviews, maturity and usage). The current algorithm is ChaCha12, //! which is well established and rigorously analysed. -//! [`StdRng`] provides the algorithm used by [`ThreadRng`] but without -//! periodic reseeding. -//! - [`SmallRng`] is an **insecure** PRNG designed to be fast, simple, require -//! little memory, and have good output quality. +//! [`StdRng`] is the deterministic generator used by [`ThreadRng`] but +//! without the periodic reseeding or thread-local management. +//! - [`SmallRng`] is a relatively simple, insecure generator designed to be +//! fast, use little memory, and pass various statistical tests of +//! randomness quality. //! //! The algorithms selected for [`StdRng`] and [`SmallRng`] may change in any -//! release and may be platform-dependent, therefore they should be considered -//! **not reproducible**. +//! release and may be platform-dependent, therefore they are not +//! [reproducible][portable]. +//! +//! ### Additional generators //! -//! ## Additional generators +//! - The [`rdrand`] crate provides an interface to the RDRAND and RDSEED +//! instructions available in modern Intel and AMD CPUs. +//! - The [`rand_jitter`] crate provides a user-space implementation of +//! entropy harvesting from CPU timer jitter, but is very slow and has +//! [security issues](https://github.com/rust-random/rand/issues/699). +//! - The [`rand_chacha`] crate provides [portable] implementations of +//! generators derived from the [ChaCha] family of stream ciphers +//! - The [`rand_pcg`] crate provides [portable] implementations of a subset +//! of the [PCG] family of small, insecure generators +//! - The [`rand_xoshiro`] crate provides [portable] implementations of the +//! [xoshiro] family of small, insecure generators //! -//! **TRNGs**: The [`rdrand`] crate provides an interface to the RDRAND and -//! RDSEED instructions available in modern Intel and AMD CPUs. -//! The [`rand_jitter`] crate provides a user-space implementation of -//! entropy harvesting from CPU timer jitter, but is very slow and has -//! [security issues](https://github.com/rust-random/rand/issues/699). +//! For more, search [crates with the `rng` tag]. +//! +//! ## Traits and functionality //! -//! **PRNGs**: Several companion crates are available, providing individual or -//! families of PRNG algorithms. These provide the implementations behind -//! [`StdRng`] and [`SmallRng`] but can also be used directly, indeed *should* -//! be used directly when **reproducibility** matters. -//! Some suggestions are: [`rand_chacha`], [`rand_pcg`], [`rand_xoshiro`]. -//! A full list can be found by searching for crates with the [`rng` tag]. +//! All generators implement [`RngCore`] and thus also [`Rng`][crate::Rng]. +//! See also the [Random Values] chapter in the book. +//! +//! Secure RNGs may additionally implement the [`CryptoRng`] trait. +//! +//! Use the [`rand_core`] crate when implementing your own RNGs. //! +//! [portable]: https://rust-random.github.io/book/crate-reprod.html +//! [Types of generators]: https://rust-random.github.io/book/guide-gen.html +//! [Our RNGs]: https://rust-random.github.io/book/guide-rngs.html +//! [Random Values]: https://rust-random.github.io/book/guide-values.html //! [`Rng`]: crate::Rng //! [`RngCore`]: crate::RngCore //! [`CryptoRng`]: crate::CryptoRng @@ -94,7 +73,10 @@ //! [`rand_chacha`]: https://crates.io/crates/rand_chacha //! [`rand_pcg`]: https://crates.io/crates/rand_pcg //! [`rand_xoshiro`]: https://crates.io/crates/rand_xoshiro -//! [`rng` tag]: https://crates.io/keywords/rng +//! [crates with the `rng` tag]: https://crates.io/keywords/rng +//! [chacha]: https://cr.yp.to/chacha.html +//! [PCG]: https://www.pcg-random.org/ +//! [xoshiro]: https://prng.di.unimi.it/ mod reseeding; pub use reseeding::ReseedingRng; diff --git a/src/rngs/small.rs b/src/rngs/small.rs index b53e34430e0..835eadc0bca 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -15,32 +15,86 @@ type Rng = super::xoshiro256plusplus::Xoshiro256PlusPlus; #[cfg(not(target_pointer_width = "64"))] type Rng = super::xoshiro128plusplus::Xoshiro128PlusPlus; -/// A small-state, fast non-crypto PRNG +/// A small-state, fast, non-crypto, non-portable PRNG /// -/// `SmallRng` may be a good choice when a PRNG with small state, cheap -/// initialization, good statistical quality and good performance are required. -/// Note that depending on the application, [`StdRng`] may be faster on many -/// modern platforms while providing higher-quality randomness. Furthermore, -/// `SmallRng` is **not** a good choice when: +/// This is the "standard small" RNG, a generator with the following properties: /// -/// - Portability is required. Its implementation is not fixed. Use a named -/// generator from an external crate instead, for example [rand_xoshiro] or -/// [rand_chacha]. Refer also to -/// [The Book](https://rust-random.github.io/book/guide-rngs.html). -/// - Security against prediction is important. Use [`StdRng`] instead. +/// - Non-[portable]: any future library version may replace the algorithm +/// and results may be platform-dependent. +/// (For a small portable generator, use the [rand_pcg] or [rand_xoshiro] crate.) +/// - Non-cryptographic: output is easy to predict (insecure) +/// - [Quality]: statistically good quality +/// - Fast: the RNG is fast for both bulk generation and single values, with +/// consistent cost of method calls +/// - Fast initialization +/// - Small state: little memory usage (current state size is 16-32 bytes +/// depending on platform) /// -/// The PRNG algorithm in `SmallRng` is chosen to be efficient on the current -/// platform, without consideration for cryptography or security. The size of -/// its state is much smaller than [`StdRng`]. The current algorithm is +/// The current algorithm is /// `Xoshiro256PlusPlus` on 64-bit platforms and `Xoshiro128PlusPlus` on 32-bit /// platforms. Both are also implemented by the [rand_xoshiro] crate. /// +/// ## Seeding (construction) +/// +/// This generator implements the [`SeedableRng`] trait. All methods are +/// suitable for seeding, but note that, even with a fixed seed, output is not +/// [portable]. Some suggestions: +/// +/// 1. Seed **from an integer** via `seed_from_u64`. This uses a hash function +/// internally to yield a (typically) good seed from any input. +/// ``` +/// # use rand::{SeedableRng, rngs::SmallRng}; +/// let rng = SmallRng::seed_from_u64(1); +/// # let _: SmallRng = rng; +/// ``` +/// 2. With a fresh seed, **direct from the OS** (implies a syscall): +/// ``` +/// # use rand::{SeedableRng, rngs::SmallRng}; +/// let rng = SmallRng::from_os_rng(); +/// # let _: SmallRng = rng; +/// ``` +/// 3. Via [`SmallRng::from_thread_rng`]: +/// ``` +/// # use rand::rngs::SmallRng; +/// let rng = SmallRng::from_thread_rng(); +/// ``` +/// +/// See also [Seeding RNGs] in the book. +/// +/// ## Generation +/// +/// The generators implements [`RngCore`] and thus also [`Rng`][crate::Rng]. +/// See also the [Random Values] chapter in the book. +/// +/// [portable]: https://rust-random.github.io/book/crate-reprod.html +/// [Seeding RNGs]: https://rust-random.github.io/book/guide-seeding.html +/// [Random Values]: https://rust-random.github.io/book/guide-values.html +/// [Quality]: https://rust-random.github.io/book/guide-rngs.html#quality /// [`StdRng`]: crate::rngs::StdRng -/// [rand_chacha]: https://crates.io/crates/rand_chacha +/// [rand_pcg]: https://crates.io/crates/rand_pcg /// [rand_xoshiro]: https://crates.io/crates/rand_xoshiro +/// [`rand_chacha::ChaCha8Rng`]: https://docs.rs/rand_chacha/latest/rand_chacha/struct.ChaCha8Rng.html #[derive(Clone, Debug, PartialEq, Eq)] pub struct SmallRng(Rng); +impl SeedableRng for SmallRng { + // Fix to 256 bits. Changing this is a breaking change! + type Seed = [u8; 32]; + + #[inline(always)] + fn from_seed(seed: Self::Seed) -> Self { + // With MSRV >= 1.77: let seed = *seed.first_chunk().unwrap(); + const LEN: usize = core::mem::size_of::<::Seed>(); + let seed = (&seed[..LEN]).try_into().unwrap(); + SmallRng(Rng::from_seed(seed)) + } + + #[inline(always)] + fn seed_from_u64(state: u64) -> Self { + SmallRng(Rng::seed_from_u64(state)) + } +} + impl RngCore for SmallRng { #[inline(always)] fn next_u32(&mut self) -> u32 { @@ -61,21 +115,6 @@ impl RngCore for SmallRng { rand_core::impl_try_rng_from_rng_core!(SmallRng); impl SmallRng { - /// Construct an instance seeded from another `Rng` - /// - /// We recommend that the source (master) RNG uses a different algorithm - /// (i.e. is not `SmallRng`) to avoid correlations between the child PRNGs. - /// - /// # Example - /// ``` - /// # use rand::rngs::SmallRng; - /// let rng = SmallRng::from_rng(rand::thread_rng()); - /// ``` - #[inline(always)] - pub fn from_rng(rng: R) -> Self { - Self(Rng::from_rng(rng)) - } - /// Construct an instance seeded from the thread-local RNG /// /// # Panics @@ -89,24 +128,4 @@ impl SmallRng { crate::thread_rng().fill_bytes(seed.as_mut()); SmallRng(Rng::from_seed(seed)) } - - /// Construct an instance from a `u64` seed - /// - /// This provides a convenient method of seeding a `SmallRng` from a simple - /// number by use of another algorithm to mutate and expand the input. - /// This is suitable for use with low Hamming Weight numbers like 0 and 1. - /// - /// **Warning:** the implementation is deterministic but not portable: - /// output values may differ according to platform and may be changed by a - /// future version of the library. - /// - /// # Example - /// ``` - /// # use rand::rngs::SmallRng; - /// let rng = SmallRng::seed_from_u64(1); - /// ``` - #[inline(always)] - pub fn seed_from_u64(state: u64) -> Self { - SmallRng(Rng::seed_from_u64(state)) - } } diff --git a/src/rngs/std.rs b/src/rngs/std.rs index 57b9bfeffde..7483becb71f 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -15,19 +15,48 @@ pub(crate) use rand_chacha::ChaCha12Core as Core; use rand_chacha::ChaCha12Rng as Rng; -/// The standard RNG. The PRNG algorithm in `StdRng` is chosen to be efficient -/// on the current platform, to be statistically strong and unpredictable -/// (meaning a cryptographically secure PRNG). +/// A strong, fast (amortized), non-portable RNG +/// +/// This is the "standard" RNG, a generator with the following properties: +/// +/// - Non-[portable]: any future library version may replace the algorithm +/// and results may be platform-dependent. +/// (For a portable version, use the [rand_chacha] crate directly.) +/// - [CSPRNG]: statistically good quality of randomness and [unpredictable] +/// - Fast ([amortized](https://en.wikipedia.org/wiki/Amortized_analysis)): +/// the RNG is fast for bulk generation, but the cost of method calls is not +/// consistent due to usage of an output buffer. /// /// The current algorithm used is the ChaCha block cipher with 12 rounds. Please /// see this relevant [rand issue] for the discussion. This may change as new /// evidence of cipher security and performance becomes available. /// -/// The algorithm is deterministic but should not be considered reproducible -/// due to dependence on configuration and possible replacement in future -/// library versions. For a secure reproducible generator, we recommend use of -/// the [rand_chacha] crate directly. +/// ## Seeding (construction) +/// +/// This generator implements the [`SeedableRng`] trait. Any method may be used, +/// but note that `seed_from_u64` is not suitable for usage where security is +/// important. Also note that, even with a fixed seed, output is not [portable]. +/// +/// It is suggested to use a fresh seed **direct from the OS** as the most +/// secure and convenient option: +/// ``` +/// # use rand::{SeedableRng, rngs::StdRng}; +/// let rng = StdRng::from_os_rng(); +/// # let _: StdRng = rng; +/// ``` +/// +/// See also [Seeding RNGs] in the book. +/// +/// ## Generation +/// +/// The generators implements [`RngCore`] and thus also [`Rng`][crate::Rng]. +/// See also the [Random Values] chapter in the book. /// +/// [portable]: https://rust-random.github.io/book/crate-reprod.html +/// [Seeding RNGs]: https://rust-random.github.io/book/guide-seeding.html +/// [unpredictable]: https://rust-random.github.io/book/guide-rngs.html#security +/// [Random Values]: https://rust-random.github.io/book/guide-values.html +/// [CSPRNG]: https://rust-random.github.io/book/guide-gen.html#cryptographically-secure-pseudo-random-number-generator /// [rand_chacha]: https://crates.io/crates/rand_chacha /// [rand issue]: https://github.com/rust-random/rand/issues/932 #[derive(Clone, Debug, PartialEq, Eq)] From 6a4650691fa1ed8d7c5b0223b408b92ca3a64abd Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Fri, 24 May 2024 17:03:57 +0300 Subject: [PATCH 388/443] Fix benches CI job (#1456) --- .github/workflows/benches.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index 118a1765406..f0112ec88d4 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -9,6 +9,9 @@ on: jobs: benches: runs-on: ubuntu-latest + defaults: + run: + working-directory: ./benches steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master @@ -16,8 +19,8 @@ jobs: toolchain: nightly components: clippy, rustfmt - name: Rustfmt - run: cargo fmt --all -- --check + run: cargo fmt -- --check - name: Clippy - run: cargo clippy --all --all-targets -- -D warnings + run: cargo clippy --all-targets -- -D warnings - name: Build run: RUSTFLAGS=-Dwarnings cargo build --all-targets From ef75e56cf5824d33c55622bf84a70ec6e22761ba Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Sat, 25 May 2024 17:17:43 +0300 Subject: [PATCH 389/443] Fix Nightly Clippy lints (#1457) --- rand_distr/src/lib.rs | 2 +- src/distributions/weighted_index.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 0d9c6c01a70..2394f54997f 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -35,7 +35,7 @@ //! //! - The [`Distribution`] trait and [`DistIter`] helper type //! - The [`Standard`], [`Alphanumeric`], [`Uniform`], [`OpenClosed01`], -//! [`Open01`], [`Bernoulli`], and [`WeightedIndex`] distributions +//! [`Open01`], [`Bernoulli`], and [`WeightedIndex`] distributions //! //! ## Distributions //! diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index ec3bbade050..48f40bebc30 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -35,10 +35,10 @@ use serde::{Deserialize, Serialize}; /// Time complexity of sampling from `WeightedIndex` is `O(log N)` where /// `N` is the number of weights. There are two alternative implementations with /// different runtimes characteristics: -/// * [`rand_distr::weighted_alias`](https://docs.rs/rand_distr/*/rand_distr/weighted_alias/index.html) -/// supports `O(1)` sampling, but with much higher initialisation cost. -/// * [`rand_distr::weighted_tree`](https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html) -/// keeps the weights in a tree structure where sampling and updating is `O(log N)`. +/// * [`rand_distr::weighted_alias`] supports `O(1)` sampling, but with much higher +/// initialisation cost. +/// * [`rand_distr::weighted_tree`] keeps the weights in a tree structure where sampling +/// and updating is `O(log N)`. /// /// A `WeightedIndex` contains a `Vec` and a [`Uniform`] and so its /// size is the sum of the size of those objects, possibly plus some alignment. @@ -80,6 +80,8 @@ use serde::{Deserialize, Serialize}; /// /// [`Uniform`]: crate::distributions::Uniform /// [`RngCore`]: crate::RngCore +/// [`rand_distr::weighted_alias`]: https://docs.rs/rand_distr/*/rand_distr/weighted_alias/index.html +/// [`rand_distr::weighted_tree`]: https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct WeightedIndex { From ca9e1198596ef766714c4c4fdb0070a45334b077 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 4 Jun 2024 09:32:57 +0100 Subject: [PATCH 390/443] Add IndexedRandom::choose_multiple_array, index::sample_array (#1453) * New private module rand::seq::iterator * New private module rand::seq::slice * Add index::sample_array and IndexedRandom::choose_multiple_array --- CHANGELOG.md | 1 + src/seq/index.rs | 72 ++- src/seq/iterator.rs | 658 +++++++++++++++++++++ src/seq/mod.rs | 1377 +------------------------------------------ src/seq/slice.rs | 765 ++++++++++++++++++++++++ 5 files changed, 1484 insertions(+), 1389 deletions(-) create mode 100644 src/seq/iterator.rs create mode 100644 src/seq/slice.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index ae5f29f8411..18ce72a533f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [Unreleased] - Add `rand::distributions::WeightedIndex::{weight, weights, total_weight}` (#1420) +- Add `IndexedRandom::choose_multiple_array`, `index::sample_array` (#1453) - Bump the MSRV to 1.61.0 - Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) - Move all benchmarks to new `benches` crate (#1439) diff --git a/src/seq/index.rs b/src/seq/index.rs index 42b810f068c..471e87c1e2c 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -7,35 +7,29 @@ // except according to those terms. //! Low-level API for sampling indices -use core::{cmp::Ordering, hash::Hash, ops::AddAssign}; - -#[cfg(feature = "alloc")] -use core::slice; - #[cfg(feature = "alloc")] use alloc::vec::{self, Vec}; +use core::slice; +use core::{hash::Hash, ops::AddAssign}; // BTreeMap is not as fast in tests, but better than nothing. -#[cfg(all(feature = "alloc", not(feature = "std")))] -use alloc::collections::BTreeSet; -#[cfg(feature = "std")] -use std::collections::HashSet; - #[cfg(feature = "std")] use super::WeightError; - +use crate::distributions::uniform::SampleUniform; #[cfg(feature = "alloc")] -use crate::{ - distributions::{uniform::SampleUniform, Distribution, Uniform}, - Rng, -}; - +use crate::distributions::{Distribution, Uniform}; +use crate::Rng; +#[cfg(all(feature = "alloc", not(feature = "std")))] +use alloc::collections::BTreeSet; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "std")] +use std::collections::HashSet; /// A vector of indices. /// /// Multiple internal representations are possible. #[derive(Clone, Debug)] +#[cfg(feature = "alloc")] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum IndexVec { #[doc(hidden)] @@ -44,6 +38,7 @@ pub enum IndexVec { USize(Vec), } +#[cfg(feature = "alloc")] impl IndexVec { /// Returns the number of indices #[inline] @@ -94,6 +89,7 @@ impl IndexVec { } } +#[cfg(feature = "alloc")] impl IntoIterator for IndexVec { type IntoIter = IndexVecIntoIter; type Item = usize; @@ -108,6 +104,7 @@ impl IntoIterator for IndexVec { } } +#[cfg(feature = "alloc")] impl PartialEq for IndexVec { fn eq(&self, other: &IndexVec) -> bool { use self::IndexVec::*; @@ -124,6 +121,7 @@ impl PartialEq for IndexVec { } } +#[cfg(feature = "alloc")] impl From> for IndexVec { #[inline] fn from(v: Vec) -> Self { @@ -131,6 +129,7 @@ impl From> for IndexVec { } } +#[cfg(feature = "alloc")] impl From> for IndexVec { #[inline] fn from(v: Vec) -> Self { @@ -171,6 +170,7 @@ impl<'a> Iterator for IndexVecIter<'a> { impl<'a> ExactSizeIterator for IndexVecIter<'a> {} /// Return type of `IndexVec::into_iter`. +#[cfg(feature = "alloc")] #[derive(Clone, Debug)] pub enum IndexVecIntoIter { #[doc(hidden)] @@ -179,6 +179,7 @@ pub enum IndexVecIntoIter { USize(vec::IntoIter), } +#[cfg(feature = "alloc")] impl Iterator for IndexVecIntoIter { type Item = usize; @@ -201,6 +202,7 @@ impl Iterator for IndexVecIntoIter { } } +#[cfg(feature = "alloc")] impl ExactSizeIterator for IndexVecIntoIter {} /// Randomly sample exactly `amount` distinct indices from `0..length`, and @@ -225,6 +227,7 @@ impl ExactSizeIterator for IndexVecIntoIter {} /// to adapt the internal `sample_floyd` implementation. /// /// Panics if `amount > length`. +#[cfg(feature = "alloc")] #[track_caller] pub fn sample(rng: &mut R, length: usize, amount: usize) -> IndexVec where @@ -267,6 +270,33 @@ where } } +/// Randomly sample exactly `N` distinct indices from `0..len`, and +/// return them in random order (fully shuffled). +/// +/// This is implemented via Floyd's algorithm. Time complexity is `O(N^2)` +/// and memory complexity is `O(N)`. +/// +/// Returns `None` if (and only if) `N > len`. +pub fn sample_array(rng: &mut R, len: usize) -> Option<[usize; N]> +where + R: Rng + ?Sized, +{ + if N > len { + return None; + } + + // Floyd's algorithm + let mut indices = [0; N]; + for (i, j) in (len - N..len).enumerate() { + let t = rng.gen_range(0..=j); + if let Some(pos) = indices[0..i].iter().position(|&x| x == t) { + indices[pos] = j; + } + indices[i] = t; + } + Some(indices) +} + /// Randomly sample exactly `amount` distinct indices from `0..length`, and /// return them in an arbitrary order (there is no guarantee of shuffling or /// ordering). The weights are to be provided by the input function `weights`, @@ -329,6 +359,8 @@ where N: UInt, IndexVec: From>, { + use std::cmp::Ordering; + if amount == N::zero() { return Ok(IndexVec::U32(Vec::new())); } @@ -399,6 +431,7 @@ where /// The output values are fully shuffled. (Overhead is under 50%.) /// /// This implementation uses `O(amount)` memory and `O(amount^2)` time. +#[cfg(feature = "alloc")] fn sample_floyd(rng: &mut R, length: u32, amount: u32) -> IndexVec where R: Rng + ?Sized, @@ -430,6 +463,7 @@ where /// performance in all cases). /// /// Set-up is `O(length)` time and memory and shuffling is `O(amount)` time. +#[cfg(feature = "alloc")] fn sample_inplace(rng: &mut R, length: u32, amount: u32) -> IndexVec where R: Rng + ?Sized, @@ -495,6 +529,7 @@ impl UInt for usize { /// /// This function is generic over X primarily so that results are value-stable /// over 32-bit and 64-bit platforms. +#[cfg(feature = "alloc")] fn sample_rejection(rng: &mut R, length: X, amount: X) -> IndexVec where R: Rng + ?Sized, @@ -519,9 +554,11 @@ where IndexVec::from(indices) } +#[cfg(feature = "alloc")] #[cfg(test)] mod test { use super::*; + use alloc::vec; #[test] #[cfg(feature = "serde1")] @@ -542,9 +579,6 @@ mod test { } } - #[cfg(feature = "alloc")] - use alloc::vec; - #[test] fn test_sample_boundaries() { let mut r = crate::test::rng(404); diff --git a/src/seq/iterator.rs b/src/seq/iterator.rs new file mode 100644 index 00000000000..46ecdfeac83 --- /dev/null +++ b/src/seq/iterator.rs @@ -0,0 +1,658 @@ +// Copyright 2018-2024 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! `IteratorRandom` + +use super::coin_flipper::CoinFlipper; +use super::gen_index; +#[allow(unused)] +use super::IndexedRandom; +use crate::Rng; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +/// Extension trait on iterators, providing random sampling methods. +/// +/// This trait is implemented on all iterators `I` where `I: Iterator + Sized` +/// and provides methods for +/// choosing one or more elements. You must `use` this trait: +/// +/// ``` +/// use rand::seq::IteratorRandom; +/// +/// let mut rng = rand::thread_rng(); +/// +/// let faces = "😀😎😐😕😠😢"; +/// println!("I am {}!", faces.chars().choose(&mut rng).unwrap()); +/// ``` +/// Example output (non-deterministic): +/// ```none +/// I am 😀! +/// ``` +pub trait IteratorRandom: Iterator + Sized { + /// Uniformly sample one element + /// + /// Assuming that the [`Iterator::size_hint`] is correct, this method + /// returns one uniformly-sampled random element of the slice, or `None` + /// only if the slice is empty. Incorrect bounds on the `size_hint` may + /// cause this method to incorrectly return `None` if fewer elements than + /// the advertised `lower` bound are present and may prevent sampling of + /// elements beyond an advertised `upper` bound (i.e. incorrect `size_hint` + /// is memory-safe, but may result in unexpected `None` result and + /// non-uniform distribution). + /// + /// With an accurate [`Iterator::size_hint`] and where [`Iterator::nth`] is + /// a constant-time operation, this method can offer `O(1)` performance. + /// Where no size hint is + /// available, complexity is `O(n)` where `n` is the iterator length. + /// Partial hints (where `lower > 0`) also improve performance. + /// + /// Note further that [`Iterator::size_hint`] may affect the number of RNG + /// samples used as well as the result (while remaining uniform sampling). + /// Consider instead using [`IteratorRandom::choose_stable`] to avoid + /// [`Iterator`] combinators which only change size hints from affecting the + /// results. + fn choose(mut self, rng: &mut R) -> Option + where + R: Rng + ?Sized, + { + let (mut lower, mut upper) = self.size_hint(); + let mut result = None; + + // Handling for this condition outside the loop allows the optimizer to eliminate the loop + // when the Iterator is an ExactSizeIterator. This has a large performance impact on e.g. + // seq_iter_choose_from_1000. + if upper == Some(lower) { + return match lower { + 0 => None, + 1 => self.next(), + _ => self.nth(gen_index(rng, lower)), + }; + } + + let mut coin_flipper = CoinFlipper::new(rng); + let mut consumed = 0; + + // Continue until the iterator is exhausted + loop { + if lower > 1 { + let ix = gen_index(coin_flipper.rng, lower + consumed); + let skip = if ix < lower { + result = self.nth(ix); + lower - (ix + 1) + } else { + lower + }; + if upper == Some(lower) { + return result; + } + consumed += lower; + if skip > 0 { + self.nth(skip - 1); + } + } else { + let elem = self.next(); + if elem.is_none() { + return result; + } + consumed += 1; + if coin_flipper.gen_ratio_one_over(consumed) { + result = elem; + } + } + + let hint = self.size_hint(); + lower = hint.0; + upper = hint.1; + } + } + + /// Uniformly sample one element (stable) + /// + /// This method is very similar to [`choose`] except that the result + /// only depends on the length of the iterator and the values produced by + /// `rng`. Notably for any iterator of a given length this will make the + /// same requests to `rng` and if the same sequence of values are produced + /// the same index will be selected from `self`. This may be useful if you + /// need consistent results no matter what type of iterator you are working + /// with. If you do not need this stability prefer [`choose`]. + /// + /// Note that this method still uses [`Iterator::size_hint`] to skip + /// constructing elements where possible, however the selection and `rng` + /// calls are the same in the face of this optimization. If you want to + /// force every element to be created regardless call `.inspect(|e| ())`. + /// + /// [`choose`]: IteratorRandom::choose + fn choose_stable(mut self, rng: &mut R) -> Option + where + R: Rng + ?Sized, + { + let mut consumed = 0; + let mut result = None; + let mut coin_flipper = CoinFlipper::new(rng); + + loop { + // Currently the only way to skip elements is `nth()`. So we need to + // store what index to access next here. + // This should be replaced by `advance_by()` once it is stable: + // https://github.com/rust-lang/rust/issues/77404 + let mut next = 0; + + let (lower, _) = self.size_hint(); + if lower >= 2 { + let highest_selected = (0..lower) + .filter(|ix| coin_flipper.gen_ratio_one_over(consumed + ix + 1)) + .last(); + + consumed += lower; + next = lower; + + if let Some(ix) = highest_selected { + result = self.nth(ix); + next -= ix + 1; + debug_assert!(result.is_some(), "iterator shorter than size_hint().0"); + } + } + + let elem = self.nth(next); + if elem.is_none() { + return result; + } + + if coin_flipper.gen_ratio_one_over(consumed + 1) { + result = elem; + } + consumed += 1; + } + } + + /// Uniformly sample `amount` distinct elements into a buffer + /// + /// Collects values at random from the iterator into a supplied buffer + /// until that buffer is filled. + /// + /// Although the elements are selected randomly, the order of elements in + /// the buffer is neither stable nor fully random. If random ordering is + /// desired, shuffle the result. + /// + /// Returns the number of elements added to the buffer. This equals the length + /// of the buffer unless the iterator contains insufficient elements, in which + /// case this equals the number of elements available. + /// + /// Complexity is `O(n)` where `n` is the length of the iterator. + /// For slices, prefer [`IndexedRandom::choose_multiple`]. + fn choose_multiple_fill(mut self, rng: &mut R, buf: &mut [Self::Item]) -> usize + where + R: Rng + ?Sized, + { + let amount = buf.len(); + let mut len = 0; + while len < amount { + if let Some(elem) = self.next() { + buf[len] = elem; + len += 1; + } else { + // Iterator exhausted; stop early + return len; + } + } + + // Continue, since the iterator was not exhausted + for (i, elem) in self.enumerate() { + let k = gen_index(rng, i + 1 + amount); + if let Some(slot) = buf.get_mut(k) { + *slot = elem; + } + } + len + } + + /// Uniformly sample `amount` distinct elements into a [`Vec`] + /// + /// This is equivalent to `choose_multiple_fill` except for the result type. + /// + /// Although the elements are selected randomly, the order of elements in + /// the buffer is neither stable nor fully random. If random ordering is + /// desired, shuffle the result. + /// + /// The length of the returned vector equals `amount` unless the iterator + /// contains insufficient elements, in which case it equals the number of + /// elements available. + /// + /// Complexity is `O(n)` where `n` is the length of the iterator. + /// For slices, prefer [`IndexedRandom::choose_multiple`]. + #[cfg(feature = "alloc")] + fn choose_multiple(mut self, rng: &mut R, amount: usize) -> Vec + where + R: Rng + ?Sized, + { + let mut reservoir = Vec::with_capacity(amount); + reservoir.extend(self.by_ref().take(amount)); + + // Continue unless the iterator was exhausted + // + // note: this prevents iterators that "restart" from causing problems. + // If the iterator stops once, then so do we. + if reservoir.len() == amount { + for (i, elem) in self.enumerate() { + let k = gen_index(rng, i + 1 + amount); + if let Some(slot) = reservoir.get_mut(k) { + *slot = elem; + } + } + } else { + // Don't hang onto extra memory. There is a corner case where + // `amount` was much less than `self.len()`. + reservoir.shrink_to_fit(); + } + reservoir + } +} + +impl IteratorRandom for I where I: Iterator + Sized {} + +#[cfg(test)] +mod test { + use super::*; + #[cfg(all(feature = "alloc", not(feature = "std")))] + use alloc::vec::Vec; + + #[derive(Clone)] + struct UnhintedIterator { + iter: I, + } + impl Iterator for UnhintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } + } + + #[derive(Clone)] + struct ChunkHintedIterator { + iter: I, + chunk_remaining: usize, + chunk_size: usize, + hint_total_size: bool, + } + impl Iterator for ChunkHintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + if self.chunk_remaining == 0 { + self.chunk_remaining = core::cmp::min(self.chunk_size, self.iter.len()); + } + self.chunk_remaining = self.chunk_remaining.saturating_sub(1); + + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + ( + self.chunk_remaining, + if self.hint_total_size { + Some(self.iter.len()) + } else { + None + }, + ) + } + } + + #[derive(Clone)] + struct WindowHintedIterator { + iter: I, + window_size: usize, + hint_total_size: bool, + } + impl Iterator for WindowHintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + ( + core::cmp::min(self.iter.len(), self.window_size), + if self.hint_total_size { + Some(self.iter.len()) + } else { + None + }, + ) + } + } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_iterator_choose() { + let r = &mut crate::test::rng(109); + fn test_iter + Clone>(r: &mut R, iter: Iter) { + let mut chosen = [0i32; 9]; + for _ in 0..1000 { + let picked = iter.clone().choose(r).unwrap(); + chosen[picked] += 1; + } + for count in chosen.iter() { + // Samples should follow Binomial(1000, 1/9) + // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x + // Note: have seen 153, which is unlikely but not impossible. + assert!( + 72 < *count && *count < 154, + "count not close to 1000/9: {}", + count + ); + } + } + + test_iter(r, 0..9); + test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); + #[cfg(feature = "alloc")] + test_iter(r, (0..9).collect::>().into_iter()); + test_iter(r, UnhintedIterator { iter: 0..9 }); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }, + ); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }, + ); + + assert_eq!((0..0).choose(r), None); + assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); + } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_iterator_choose_stable() { + let r = &mut crate::test::rng(109); + fn test_iter + Clone>(r: &mut R, iter: Iter) { + let mut chosen = [0i32; 9]; + for _ in 0..1000 { + let picked = iter.clone().choose_stable(r).unwrap(); + chosen[picked] += 1; + } + for count in chosen.iter() { + // Samples should follow Binomial(1000, 1/9) + // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x + // Note: have seen 153, which is unlikely but not impossible. + assert!( + 72 < *count && *count < 154, + "count not close to 1000/9: {}", + count + ); + } + } + + test_iter(r, 0..9); + test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); + #[cfg(feature = "alloc")] + test_iter(r, (0..9).collect::>().into_iter()); + test_iter(r, UnhintedIterator { iter: 0..9 }); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }, + ); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }, + ); + + assert_eq!((0..0).choose(r), None); + assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); + } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_iterator_choose_stable_stability() { + fn test_iter(iter: impl Iterator + Clone) -> [i32; 9] { + let r = &mut crate::test::rng(109); + let mut chosen = [0i32; 9]; + for _ in 0..1000 { + let picked = iter.clone().choose_stable(r).unwrap(); + chosen[picked] += 1; + } + chosen + } + + let reference = test_iter(0..9); + assert_eq!( + test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), + reference + ); + + #[cfg(feature = "alloc")] + assert_eq!(test_iter((0..9).collect::>().into_iter()), reference); + assert_eq!(test_iter(UnhintedIterator { iter: 0..9 }), reference); + assert_eq!( + test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }), + reference + ); + assert_eq!( + test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }), + reference + ); + assert_eq!( + test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }), + reference + ); + assert_eq!( + test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }), + reference + ); + } + + #[test] + #[cfg(feature = "alloc")] + fn test_sample_iter() { + let min_val = 1; + let max_val = 100; + + let mut r = crate::test::rng(401); + let vals = (min_val..max_val).collect::>(); + let small_sample = vals.iter().choose_multiple(&mut r, 5); + let large_sample = vals.iter().choose_multiple(&mut r, vals.len() + 5); + + assert_eq!(small_sample.len(), 5); + assert_eq!(large_sample.len(), vals.len()); + // no randomization happens when amount >= len + assert_eq!(large_sample, vals.iter().collect::>()); + + assert!(small_sample + .iter() + .all(|e| { **e >= min_val && **e <= max_val })); + } + + #[test] + fn value_stability_choose() { + fn choose>(iter: I) -> Option { + let mut rng = crate::test::rng(411); + iter.choose(&mut rng) + } + + assert_eq!(choose([].iter().cloned()), None); + assert_eq!(choose(0..100), Some(33)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); + assert_eq!( + choose(ChunkHintedIterator { + iter: 0..100, + chunk_size: 32, + chunk_remaining: 32, + hint_total_size: false, + }), + Some(91) + ); + assert_eq!( + choose(ChunkHintedIterator { + iter: 0..100, + chunk_size: 32, + chunk_remaining: 32, + hint_total_size: true, + }), + Some(91) + ); + assert_eq!( + choose(WindowHintedIterator { + iter: 0..100, + window_size: 32, + hint_total_size: false, + }), + Some(34) + ); + assert_eq!( + choose(WindowHintedIterator { + iter: 0..100, + window_size: 32, + hint_total_size: true, + }), + Some(34) + ); + } + + #[test] + fn value_stability_choose_stable() { + fn choose>(iter: I) -> Option { + let mut rng = crate::test::rng(411); + iter.choose_stable(&mut rng) + } + + assert_eq!(choose([].iter().cloned()), None); + assert_eq!(choose(0..100), Some(27)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); + assert_eq!( + choose(ChunkHintedIterator { + iter: 0..100, + chunk_size: 32, + chunk_remaining: 32, + hint_total_size: false, + }), + Some(27) + ); + assert_eq!( + choose(ChunkHintedIterator { + iter: 0..100, + chunk_size: 32, + chunk_remaining: 32, + hint_total_size: true, + }), + Some(27) + ); + assert_eq!( + choose(WindowHintedIterator { + iter: 0..100, + window_size: 32, + hint_total_size: false, + }), + Some(27) + ); + assert_eq!( + choose(WindowHintedIterator { + iter: 0..100, + window_size: 32, + hint_total_size: true, + }), + Some(27) + ); + } + + #[test] + fn value_stability_choose_multiple() { + fn do_test>(iter: I, v: &[u32]) { + let mut rng = crate::test::rng(412); + let mut buf = [0u32; 8]; + assert_eq!( + iter.clone().choose_multiple_fill(&mut rng, &mut buf), + v.len() + ); + assert_eq!(&buf[0..v.len()], v); + + #[cfg(feature = "alloc")] + { + let mut rng = crate::test::rng(412); + assert_eq!(iter.choose_multiple(&mut rng, v.len()), v); + } + } + + do_test(0..4, &[0, 1, 2, 3]); + do_test(0..8, &[0, 1, 2, 3, 4, 5, 6, 7]); + do_test(0..100, &[77, 95, 38, 23, 25, 8, 58, 40]); + } +} diff --git a/src/seq/mod.rs b/src/seq/mod.rs index d8268e9a981..b7cd1729d21 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -27,675 +27,22 @@ //! small performance boost in some cases). mod coin_flipper; -#[cfg(feature = "alloc")] -pub mod index; - mod increasing_uniform; +mod iterator; +mod slice; + +pub mod index; #[cfg(feature = "alloc")] #[doc(no_inline)] pub use crate::distributions::WeightError; - -use core::ops::{Index, IndexMut}; - +pub use iterator::IteratorRandom; #[cfg(feature = "alloc")] -use alloc::vec::Vec; +pub use slice::SliceChooseIter; +pub use slice::{IndexedMutRandom, IndexedRandom, SliceRandom}; -#[cfg(feature = "alloc")] -use crate::distributions::uniform::{SampleBorrow, SampleUniform}; -#[cfg(feature = "alloc")] -use crate::distributions::Weight; use crate::Rng; -use self::coin_flipper::CoinFlipper; -use self::increasing_uniform::IncreasingUniform; - -/// Extension trait on indexable lists, providing random sampling methods. -/// -/// This trait is implemented on `[T]` slice types. Other types supporting -/// [`std::ops::Index`] may implement this (only [`Self::len`] must be -/// specified). -pub trait IndexedRandom: Index { - /// The length - fn len(&self) -> usize; - - /// True when the length is zero - #[inline] - fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Uniformly sample one element - /// - /// Returns a reference to one uniformly-sampled random element of - /// the slice, or `None` if the slice is empty. - /// - /// For slices, complexity is `O(1)`. - /// - /// # Example - /// - /// ``` - /// use rand::thread_rng; - /// use rand::seq::IndexedRandom; - /// - /// let choices = [1, 2, 4, 8, 16, 32]; - /// let mut rng = thread_rng(); - /// println!("{:?}", choices.choose(&mut rng)); - /// assert_eq!(choices[..0].choose(&mut rng), None); - /// ``` - fn choose(&self, rng: &mut R) -> Option<&Self::Output> - where - R: Rng + ?Sized, - { - if self.is_empty() { - None - } else { - Some(&self[gen_index(rng, self.len())]) - } - } - - /// Uniformly sample `amount` distinct elements - /// - /// Chooses `amount` elements from the slice at random, without repetition, - /// and in random order. The returned iterator is appropriate both for - /// collection into a `Vec` and filling an existing buffer (see example). - /// - /// In case this API is not sufficiently flexible, use [`index::sample`]. - /// - /// For slices, complexity is the same as [`index::sample`]. - /// - /// # Example - /// ``` - /// use rand::seq::IndexedRandom; - /// - /// let mut rng = &mut rand::thread_rng(); - /// let sample = "Hello, audience!".as_bytes(); - /// - /// // collect the results into a vector: - /// let v: Vec = sample.choose_multiple(&mut rng, 3).cloned().collect(); - /// - /// // store in a buffer: - /// let mut buf = [0u8; 5]; - /// for (b, slot) in sample.choose_multiple(&mut rng, buf.len()).zip(buf.iter_mut()) { - /// *slot = *b; - /// } - /// ``` - #[cfg(feature = "alloc")] - fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter - where - Self::Output: Sized, - R: Rng + ?Sized, - { - let amount = core::cmp::min(amount, self.len()); - SliceChooseIter { - slice: self, - _phantom: Default::default(), - indices: index::sample(rng, self.len(), amount).into_iter(), - } - } - - /// Biased sampling for one element - /// - /// Returns a reference to one element of the slice, sampled according - /// to the provided weights. Returns `None` only if the slice is empty. - /// - /// The specified function `weight` maps each item `x` to a relative - /// likelihood `weight(x)`. The probability of each item being selected is - /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. - /// - /// For slices of length `n`, complexity is `O(n)`. - /// For more information about the underlying algorithm, - /// see [`distributions::WeightedIndex`]. - /// - /// See also [`choose_weighted_mut`]. - /// - /// # Example - /// - /// ``` - /// use rand::prelude::*; - /// - /// let choices = [('a', 2), ('b', 1), ('c', 1), ('d', 0)]; - /// let mut rng = thread_rng(); - /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c', - /// // and 'd' will never be printed - /// println!("{:?}", choices.choose_weighted(&mut rng, |item| item.1).unwrap().0); - /// ``` - /// [`choose`]: IndexedRandom::choose - /// [`choose_weighted_mut`]: IndexedMutRandom::choose_weighted_mut - /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex - #[cfg(feature = "alloc")] - fn choose_weighted( - &self, - rng: &mut R, - weight: F, - ) -> Result<&Self::Output, WeightError> - where - R: Rng + ?Sized, - F: Fn(&Self::Output) -> B, - B: SampleBorrow, - X: SampleUniform + Weight + PartialOrd, - { - use crate::distributions::{Distribution, WeightedIndex}; - let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; - Ok(&self[distr.sample(rng)]) - } - - /// Biased sampling of `amount` distinct elements - /// - /// Similar to [`choose_multiple`], but where the likelihood of each element's - /// inclusion in the output may be specified. The elements are returned in an - /// arbitrary, unspecified order. - /// - /// The specified function `weight` maps each item `x` to a relative - /// likelihood `weight(x)`. The probability of each item being selected is - /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. - /// - /// If all of the weights are equal, even if they are all zero, each element has - /// an equal likelihood of being selected. - /// - /// This implementation uses `O(length + amount)` space and `O(length)` time - /// if the "nightly" feature is enabled, or `O(length)` space and - /// `O(length + amount * log length)` time otherwise. - /// - /// # Example - /// - /// ``` - /// use rand::prelude::*; - /// - /// let choices = [('a', 2), ('b', 1), ('c', 1)]; - /// let mut rng = thread_rng(); - /// // First Draw * Second Draw = total odds - /// // ----------------------- - /// // (50% * 50%) + (25% * 67%) = 41.7% chance that the output is `['a', 'b']` in some order. - /// // (50% * 50%) + (25% * 67%) = 41.7% chance that the output is `['a', 'c']` in some order. - /// // (25% * 33%) + (25% * 33%) = 16.6% chance that the output is `['b', 'c']` in some order. - /// println!("{:?}", choices.choose_multiple_weighted(&mut rng, 2, |item| item.1).unwrap().collect::>()); - /// ``` - /// [`choose_multiple`]: IndexedRandom::choose_multiple - // Note: this is feature-gated on std due to usage of f64::powf. - // If necessary, we may use alloc+libm as an alternative (see PR #1089). - #[cfg(feature = "std")] - fn choose_multiple_weighted( - &self, - rng: &mut R, - amount: usize, - weight: F, - ) -> Result, WeightError> - where - Self::Output: Sized, - R: Rng + ?Sized, - F: Fn(&Self::Output) -> X, - X: Into, - { - let amount = core::cmp::min(amount, self.len()); - Ok(SliceChooseIter { - slice: self, - _phantom: Default::default(), - indices: index::sample_weighted( - rng, - self.len(), - |idx| weight(&self[idx]).into(), - amount, - )? - .into_iter(), - }) - } -} - -/// Extension trait on indexable lists, providing random sampling methods. -/// -/// This trait is implemented automatically for every type implementing -/// [`IndexedRandom`] and [`std::ops::IndexMut`]. -pub trait IndexedMutRandom: IndexedRandom + IndexMut { - /// Uniformly sample one element (mut) - /// - /// Returns a mutable reference to one uniformly-sampled random element of - /// the slice, or `None` if the slice is empty. - /// - /// For slices, complexity is `O(1)`. - fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Output> - where - R: Rng + ?Sized, - { - if self.is_empty() { - None - } else { - let len = self.len(); - Some(&mut self[gen_index(rng, len)]) - } - } - - /// Biased sampling for one element (mut) - /// - /// Returns a mutable reference to one element of the slice, sampled according - /// to the provided weights. Returns `None` only if the slice is empty. - /// - /// The specified function `weight` maps each item `x` to a relative - /// likelihood `weight(x)`. The probability of each item being selected is - /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. - /// - /// For slices of length `n`, complexity is `O(n)`. - /// For more information about the underlying algorithm, - /// see [`distributions::WeightedIndex`]. - /// - /// See also [`choose_weighted`]. - /// - /// [`choose_mut`]: IndexedMutRandom::choose_mut - /// [`choose_weighted`]: IndexedRandom::choose_weighted - /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex - #[cfg(feature = "alloc")] - fn choose_weighted_mut( - &mut self, - rng: &mut R, - weight: F, - ) -> Result<&mut Self::Output, WeightError> - where - R: Rng + ?Sized, - F: Fn(&Self::Output) -> B, - B: SampleBorrow, - X: SampleUniform + Weight + PartialOrd, - { - use crate::distributions::{Distribution, WeightedIndex}; - let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; - let index = distr.sample(rng); - Ok(&mut self[index]) - } -} - -/// Extension trait on slices, providing shuffling methods. -/// -/// This trait is implemented on all `[T]` slice types, providing several -/// methods for choosing and shuffling elements. You must `use` this trait: -/// -/// ``` -/// use rand::seq::SliceRandom; -/// -/// let mut rng = rand::thread_rng(); -/// let mut bytes = "Hello, random!".to_string().into_bytes(); -/// bytes.shuffle(&mut rng); -/// let str = String::from_utf8(bytes).unwrap(); -/// println!("{}", str); -/// ``` -/// Example output (non-deterministic): -/// ```none -/// l,nmroHado !le -/// ``` -pub trait SliceRandom: IndexedMutRandom { - /// Shuffle a mutable slice in place. - /// - /// For slices of length `n`, complexity is `O(n)`. - /// The resulting permutation is picked uniformly from the set of all possible permutations. - /// - /// # Example - /// - /// ``` - /// use rand::seq::SliceRandom; - /// use rand::thread_rng; - /// - /// let mut rng = thread_rng(); - /// let mut y = [1, 2, 3, 4, 5]; - /// println!("Unshuffled: {:?}", y); - /// y.shuffle(&mut rng); - /// println!("Shuffled: {:?}", y); - /// ``` - fn shuffle(&mut self, rng: &mut R) - where - R: Rng + ?Sized; - - /// Shuffle a slice in place, but exit early. - /// - /// Returns two mutable slices from the source slice. The first contains - /// `amount` elements randomly permuted. The second has the remaining - /// elements that are not fully shuffled. - /// - /// This is an efficient method to select `amount` elements at random from - /// the slice, provided the slice may be mutated. - /// - /// If you only need to choose elements randomly and `amount > self.len()/2` - /// then you may improve performance by taking - /// `amount = self.len() - amount` and using only the second slice. - /// - /// If `amount` is greater than the number of elements in the slice, this - /// will perform a full shuffle. - /// - /// For slices, complexity is `O(m)` where `m = amount`. - fn partial_shuffle( - &mut self, - rng: &mut R, - amount: usize, - ) -> (&mut [Self::Output], &mut [Self::Output]) - where - Self::Output: Sized, - R: Rng + ?Sized; -} - -/// Extension trait on iterators, providing random sampling methods. -/// -/// This trait is implemented on all iterators `I` where `I: Iterator + Sized` -/// and provides methods for -/// choosing one or more elements. You must `use` this trait: -/// -/// ``` -/// use rand::seq::IteratorRandom; -/// -/// let mut rng = rand::thread_rng(); -/// -/// let faces = "😀😎😐😕😠😢"; -/// println!("I am {}!", faces.chars().choose(&mut rng).unwrap()); -/// ``` -/// Example output (non-deterministic): -/// ```none -/// I am 😀! -/// ``` -pub trait IteratorRandom: Iterator + Sized { - /// Uniformly sample one element - /// - /// Assuming that the [`Iterator::size_hint`] is correct, this method - /// returns one uniformly-sampled random element of the slice, or `None` - /// only if the slice is empty. Incorrect bounds on the `size_hint` may - /// cause this method to incorrectly return `None` if fewer elements than - /// the advertised `lower` bound are present and may prevent sampling of - /// elements beyond an advertised `upper` bound (i.e. incorrect `size_hint` - /// is memory-safe, but may result in unexpected `None` result and - /// non-uniform distribution). - /// - /// With an accurate [`Iterator::size_hint`] and where [`Iterator::nth`] is - /// a constant-time operation, this method can offer `O(1)` performance. - /// Where no size hint is - /// available, complexity is `O(n)` where `n` is the iterator length. - /// Partial hints (where `lower > 0`) also improve performance. - /// - /// Note further that [`Iterator::size_hint`] may affect the number of RNG - /// samples used as well as the result (while remaining uniform sampling). - /// Consider instead using [`IteratorRandom::choose_stable`] to avoid - /// [`Iterator`] combinators which only change size hints from affecting the - /// results. - fn choose(mut self, rng: &mut R) -> Option - where - R: Rng + ?Sized, - { - let (mut lower, mut upper) = self.size_hint(); - let mut result = None; - - // Handling for this condition outside the loop allows the optimizer to eliminate the loop - // when the Iterator is an ExactSizeIterator. This has a large performance impact on e.g. - // seq_iter_choose_from_1000. - if upper == Some(lower) { - return match lower { - 0 => None, - 1 => self.next(), - _ => self.nth(gen_index(rng, lower)), - }; - } - - let mut coin_flipper = CoinFlipper::new(rng); - let mut consumed = 0; - - // Continue until the iterator is exhausted - loop { - if lower > 1 { - let ix = gen_index(coin_flipper.rng, lower + consumed); - let skip = if ix < lower { - result = self.nth(ix); - lower - (ix + 1) - } else { - lower - }; - if upper == Some(lower) { - return result; - } - consumed += lower; - if skip > 0 { - self.nth(skip - 1); - } - } else { - let elem = self.next(); - if elem.is_none() { - return result; - } - consumed += 1; - if coin_flipper.gen_ratio_one_over(consumed) { - result = elem; - } - } - - let hint = self.size_hint(); - lower = hint.0; - upper = hint.1; - } - } - - /// Uniformly sample one element (stable) - /// - /// This method is very similar to [`choose`] except that the result - /// only depends on the length of the iterator and the values produced by - /// `rng`. Notably for any iterator of a given length this will make the - /// same requests to `rng` and if the same sequence of values are produced - /// the same index will be selected from `self`. This may be useful if you - /// need consistent results no matter what type of iterator you are working - /// with. If you do not need this stability prefer [`choose`]. - /// - /// Note that this method still uses [`Iterator::size_hint`] to skip - /// constructing elements where possible, however the selection and `rng` - /// calls are the same in the face of this optimization. If you want to - /// force every element to be created regardless call `.inspect(|e| ())`. - /// - /// [`choose`]: IteratorRandom::choose - fn choose_stable(mut self, rng: &mut R) -> Option - where - R: Rng + ?Sized, - { - let mut consumed = 0; - let mut result = None; - let mut coin_flipper = CoinFlipper::new(rng); - - loop { - // Currently the only way to skip elements is `nth()`. So we need to - // store what index to access next here. - // This should be replaced by `advance_by()` once it is stable: - // https://github.com/rust-lang/rust/issues/77404 - let mut next = 0; - - let (lower, _) = self.size_hint(); - if lower >= 2 { - let highest_selected = (0..lower) - .filter(|ix| coin_flipper.gen_ratio_one_over(consumed + ix + 1)) - .last(); - - consumed += lower; - next = lower; - - if let Some(ix) = highest_selected { - result = self.nth(ix); - next -= ix + 1; - debug_assert!(result.is_some(), "iterator shorter than size_hint().0"); - } - } - - let elem = self.nth(next); - if elem.is_none() { - return result; - } - - if coin_flipper.gen_ratio_one_over(consumed + 1) { - result = elem; - } - consumed += 1; - } - } - - /// Uniformly sample `amount` distinct elements into a buffer - /// - /// Collects values at random from the iterator into a supplied buffer - /// until that buffer is filled. - /// - /// Although the elements are selected randomly, the order of elements in - /// the buffer is neither stable nor fully random. If random ordering is - /// desired, shuffle the result. - /// - /// Returns the number of elements added to the buffer. This equals the length - /// of the buffer unless the iterator contains insufficient elements, in which - /// case this equals the number of elements available. - /// - /// Complexity is `O(n)` where `n` is the length of the iterator. - /// For slices, prefer [`IndexedRandom::choose_multiple`]. - fn choose_multiple_fill(mut self, rng: &mut R, buf: &mut [Self::Item]) -> usize - where - R: Rng + ?Sized, - { - let amount = buf.len(); - let mut len = 0; - while len < amount { - if let Some(elem) = self.next() { - buf[len] = elem; - len += 1; - } else { - // Iterator exhausted; stop early - return len; - } - } - - // Continue, since the iterator was not exhausted - for (i, elem) in self.enumerate() { - let k = gen_index(rng, i + 1 + amount); - if let Some(slot) = buf.get_mut(k) { - *slot = elem; - } - } - len - } - - /// Uniformly sample `amount` distinct elements into a [`Vec`] - /// - /// This is equivalent to `choose_multiple_fill` except for the result type. - /// - /// Although the elements are selected randomly, the order of elements in - /// the buffer is neither stable nor fully random. If random ordering is - /// desired, shuffle the result. - /// - /// The length of the returned vector equals `amount` unless the iterator - /// contains insufficient elements, in which case it equals the number of - /// elements available. - /// - /// Complexity is `O(n)` where `n` is the length of the iterator. - /// For slices, prefer [`IndexedRandom::choose_multiple`]. - #[cfg(feature = "alloc")] - fn choose_multiple(mut self, rng: &mut R, amount: usize) -> Vec - where - R: Rng + ?Sized, - { - let mut reservoir = Vec::with_capacity(amount); - reservoir.extend(self.by_ref().take(amount)); - - // Continue unless the iterator was exhausted - // - // note: this prevents iterators that "restart" from causing problems. - // If the iterator stops once, then so do we. - if reservoir.len() == amount { - for (i, elem) in self.enumerate() { - let k = gen_index(rng, i + 1 + amount); - if let Some(slot) = reservoir.get_mut(k) { - *slot = elem; - } - } - } else { - // Don't hang onto extra memory. There is a corner case where - // `amount` was much less than `self.len()`. - reservoir.shrink_to_fit(); - } - reservoir - } -} - -impl IndexedRandom for [T] { - fn len(&self) -> usize { - self.len() - } -} - -impl + ?Sized> IndexedMutRandom for IR {} - -impl SliceRandom for [T] { - fn shuffle(&mut self, rng: &mut R) - where - R: Rng + ?Sized, - { - if self.len() <= 1 { - // There is no need to shuffle an empty or single element slice - return; - } - self.partial_shuffle(rng, self.len()); - } - - fn partial_shuffle(&mut self, rng: &mut R, amount: usize) -> (&mut [T], &mut [T]) - where - R: Rng + ?Sized, - { - let m = self.len().saturating_sub(amount); - - // The algorithm below is based on Durstenfeld's algorithm for the - // [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) - // for an unbiased permutation. - // It ensures that the last `amount` elements of the slice - // are randomly selected from the whole slice. - - // `IncreasingUniform::next_index()` is faster than `gen_index` - // but only works for 32 bit integers - // So we must use the slow method if the slice is longer than that. - if self.len() < (u32::MAX as usize) { - let mut chooser = IncreasingUniform::new(rng, m as u32); - for i in m..self.len() { - let index = chooser.next_index(); - self.swap(i, index); - } - } else { - for i in m..self.len() { - let index = gen_index(rng, i + 1); - self.swap(i, index); - } - } - let r = self.split_at_mut(m); - (r.1, r.0) - } -} - -impl IteratorRandom for I where I: Iterator + Sized {} - -/// An iterator over multiple slice elements. -/// -/// This struct is created by -/// [`IndexedRandom::choose_multiple`](trait.IndexedRandom.html#tymethod.choose_multiple). -#[cfg(feature = "alloc")] -#[derive(Debug)] -pub struct SliceChooseIter<'a, S: ?Sized + 'a, T: 'a> { - slice: &'a S, - _phantom: core::marker::PhantomData, - indices: index::IndexVecIntoIter, -} - -#[cfg(feature = "alloc")] -impl<'a, S: Index + ?Sized + 'a, T: 'a> Iterator for SliceChooseIter<'a, S, T> { - type Item = &'a T; - - fn next(&mut self) -> Option { - // TODO: investigate using SliceIndex::get_unchecked when stable - self.indices.next().map(|i| &self.slice[i]) - } - - fn size_hint(&self) -> (usize, Option) { - (self.indices.len(), Some(self.indices.len())) - } -} - -#[cfg(feature = "alloc")] -impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator - for SliceChooseIter<'a, S, T> -{ - fn len(&self) -> usize { - self.indices.len() - } -} - // Sample a number uniformly between 0 and `ubound`. Uses 32-bit sampling where // possible, primarily in order to produce the same output on 32-bit and 64-bit // platforms. @@ -707,713 +54,3 @@ fn gen_index(rng: &mut R, ubound: usize) -> usize { rng.gen_range(0..ubound) } } - -#[cfg(test)] -mod test { - use super::*; - #[cfg(all(feature = "alloc", not(feature = "std")))] - use alloc::vec::Vec; - - #[test] - fn test_slice_choose() { - let mut r = crate::test::rng(107); - let chars = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - ]; - let mut chosen = [0i32; 14]; - // The below all use a binomial distribution with n=1000, p=1/14. - // binocdf(40, 1000, 1/14) ~= 2e-5; 1-binocdf(106, ..) ~= 2e-5 - for _ in 0..1000 { - let picked = *chars.choose(&mut r).unwrap(); - chosen[(picked as usize) - ('a' as usize)] += 1; - } - for count in chosen.iter() { - assert!(40 < *count && *count < 106); - } - - chosen.iter_mut().for_each(|x| *x = 0); - for _ in 0..1000 { - *chosen.choose_mut(&mut r).unwrap() += 1; - } - for count in chosen.iter() { - assert!(40 < *count && *count < 106); - } - - let mut v: [isize; 0] = []; - assert_eq!(v.choose(&mut r), None); - assert_eq!(v.choose_mut(&mut r), None); - } - - #[test] - fn value_stability_slice() { - let mut r = crate::test::rng(413); - let chars = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - ]; - let mut nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; - - assert_eq!(chars.choose(&mut r), Some(&'l')); - assert_eq!(nums.choose_mut(&mut r), Some(&mut 3)); - - #[cfg(feature = "alloc")] - assert_eq!( - &chars - .choose_multiple(&mut r, 8) - .cloned() - .collect::>(), - &['f', 'i', 'd', 'b', 'c', 'm', 'j', 'k'] - ); - - #[cfg(feature = "alloc")] - assert_eq!(chars.choose_weighted(&mut r, |_| 1), Ok(&'l')); - #[cfg(feature = "alloc")] - assert_eq!(nums.choose_weighted_mut(&mut r, |_| 1), Ok(&mut 8)); - - let mut r = crate::test::rng(414); - nums.shuffle(&mut r); - assert_eq!(nums, [5, 11, 0, 8, 7, 12, 6, 4, 9, 3, 1, 2, 10]); - nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; - let res = nums.partial_shuffle(&mut r, 6); - assert_eq!(res.0, &mut [7, 12, 6, 8, 1, 9]); - assert_eq!(res.1, &mut [0, 11, 2, 3, 4, 5, 10]); - } - - #[derive(Clone)] - struct UnhintedIterator { - iter: I, - } - impl Iterator for UnhintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } - } - - #[derive(Clone)] - struct ChunkHintedIterator { - iter: I, - chunk_remaining: usize, - chunk_size: usize, - hint_total_size: bool, - } - impl Iterator for ChunkHintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - if self.chunk_remaining == 0 { - self.chunk_remaining = core::cmp::min(self.chunk_size, self.iter.len()); - } - self.chunk_remaining = self.chunk_remaining.saturating_sub(1); - - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - ( - self.chunk_remaining, - if self.hint_total_size { - Some(self.iter.len()) - } else { - None - }, - ) - } - } - - #[derive(Clone)] - struct WindowHintedIterator { - iter: I, - window_size: usize, - hint_total_size: bool, - } - impl Iterator for WindowHintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - ( - core::cmp::min(self.iter.len(), self.window_size), - if self.hint_total_size { - Some(self.iter.len()) - } else { - None - }, - ) - } - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_iterator_choose() { - let r = &mut crate::test::rng(109); - fn test_iter + Clone>(r: &mut R, iter: Iter) { - let mut chosen = [0i32; 9]; - for _ in 0..1000 { - let picked = iter.clone().choose(r).unwrap(); - chosen[picked] += 1; - } - for count in chosen.iter() { - // Samples should follow Binomial(1000, 1/9) - // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x - // Note: have seen 153, which is unlikely but not impossible. - assert!( - 72 < *count && *count < 154, - "count not close to 1000/9: {}", - count - ); - } - } - - test_iter(r, 0..9); - test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); - #[cfg(feature = "alloc")] - test_iter(r, (0..9).collect::>().into_iter()); - test_iter(r, UnhintedIterator { iter: 0..9 }); - test_iter( - r, - ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }, - ); - test_iter( - r, - ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }, - ); - test_iter( - r, - WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }, - ); - test_iter( - r, - WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }, - ); - - assert_eq!((0..0).choose(r), None); - assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_iterator_choose_stable() { - let r = &mut crate::test::rng(109); - fn test_iter + Clone>(r: &mut R, iter: Iter) { - let mut chosen = [0i32; 9]; - for _ in 0..1000 { - let picked = iter.clone().choose_stable(r).unwrap(); - chosen[picked] += 1; - } - for count in chosen.iter() { - // Samples should follow Binomial(1000, 1/9) - // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x - // Note: have seen 153, which is unlikely but not impossible. - assert!( - 72 < *count && *count < 154, - "count not close to 1000/9: {}", - count - ); - } - } - - test_iter(r, 0..9); - test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); - #[cfg(feature = "alloc")] - test_iter(r, (0..9).collect::>().into_iter()); - test_iter(r, UnhintedIterator { iter: 0..9 }); - test_iter( - r, - ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }, - ); - test_iter( - r, - ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }, - ); - test_iter( - r, - WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }, - ); - test_iter( - r, - WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }, - ); - - assert_eq!((0..0).choose(r), None); - assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_iterator_choose_stable_stability() { - fn test_iter(iter: impl Iterator + Clone) -> [i32; 9] { - let r = &mut crate::test::rng(109); - let mut chosen = [0i32; 9]; - for _ in 0..1000 { - let picked = iter.clone().choose_stable(r).unwrap(); - chosen[picked] += 1; - } - chosen - } - - let reference = test_iter(0..9); - assert_eq!( - test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), - reference - ); - - #[cfg(feature = "alloc")] - assert_eq!(test_iter((0..9).collect::>().into_iter()), reference); - assert_eq!(test_iter(UnhintedIterator { iter: 0..9 }), reference); - assert_eq!( - test_iter(ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }), - reference - ); - assert_eq!( - test_iter(ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }), - reference - ); - assert_eq!( - test_iter(WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }), - reference - ); - assert_eq!( - test_iter(WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }), - reference - ); - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_shuffle() { - let mut r = crate::test::rng(108); - let empty: &mut [isize] = &mut []; - empty.shuffle(&mut r); - let mut one = [1]; - one.shuffle(&mut r); - let b: &[_] = &[1]; - assert_eq!(one, b); - - let mut two = [1, 2]; - two.shuffle(&mut r); - assert!(two == [1, 2] || two == [2, 1]); - - fn move_last(slice: &mut [usize], pos: usize) { - // use slice[pos..].rotate_left(1); once we can use that - let last_val = slice[pos]; - for i in pos..slice.len() - 1 { - slice[i] = slice[i + 1]; - } - *slice.last_mut().unwrap() = last_val; - } - let mut counts = [0i32; 24]; - for _ in 0..10000 { - let mut arr: [usize; 4] = [0, 1, 2, 3]; - arr.shuffle(&mut r); - let mut permutation = 0usize; - let mut pos_value = counts.len(); - for i in 0..4 { - pos_value /= 4 - i; - let pos = arr.iter().position(|&x| x == i).unwrap(); - assert!(pos < (4 - i)); - permutation += pos * pos_value; - move_last(&mut arr, pos); - assert_eq!(arr[3], i); - } - for (i, &a) in arr.iter().enumerate() { - assert_eq!(a, i); - } - counts[permutation] += 1; - } - for count in counts.iter() { - // Binomial(10000, 1/24) with average 416.667 - // Octave: binocdf(n, 10000, 1/24) - // 99.9% chance samples lie within this range: - assert!(352 <= *count && *count <= 483, "count: {}", count); - } - } - - #[test] - fn test_partial_shuffle() { - let mut r = crate::test::rng(118); - - let mut empty: [u32; 0] = []; - let res = empty.partial_shuffle(&mut r, 10); - assert_eq!((res.0.len(), res.1.len()), (0, 0)); - - let mut v = [1, 2, 3, 4, 5]; - let res = v.partial_shuffle(&mut r, 2); - assert_eq!((res.0.len(), res.1.len()), (2, 3)); - assert!(res.0[0] != res.0[1]); - // First elements are only modified if selected, so at least one isn't modified: - assert!(res.1[0] == 1 || res.1[1] == 2 || res.1[2] == 3); - } - - #[test] - #[cfg(feature = "alloc")] - fn test_sample_iter() { - let min_val = 1; - let max_val = 100; - - let mut r = crate::test::rng(401); - let vals = (min_val..max_val).collect::>(); - let small_sample = vals.iter().choose_multiple(&mut r, 5); - let large_sample = vals.iter().choose_multiple(&mut r, vals.len() + 5); - - assert_eq!(small_sample.len(), 5); - assert_eq!(large_sample.len(), vals.len()); - // no randomization happens when amount >= len - assert_eq!(large_sample, vals.iter().collect::>()); - - assert!(small_sample - .iter() - .all(|e| { **e >= min_val && **e <= max_val })); - } - - #[test] - #[cfg(feature = "alloc")] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_weighted() { - let mut r = crate::test::rng(406); - const N_REPS: u32 = 3000; - let weights = [1u32, 2, 3, 0, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]; - let total_weight = weights.iter().sum::() as f32; - - let verify = |result: [i32; 14]| { - for (i, count) in result.iter().enumerate() { - let exp = (weights[i] * N_REPS) as f32 / total_weight; - let mut err = (*count as f32 - exp).abs(); - if err != 0.0 { - err /= exp; - } - assert!(err <= 0.25); - } - }; - - // choose_weighted - fn get_weight(item: &(u32, T)) -> u32 { - item.0 - } - let mut chosen = [0i32; 14]; - let mut items = [(0u32, 0usize); 14]; // (weight, index) - for (i, item) in items.iter_mut().enumerate() { - *item = (weights[i], i); - } - for _ in 0..N_REPS { - let item = items.choose_weighted(&mut r, get_weight).unwrap(); - chosen[item.1] += 1; - } - verify(chosen); - - // choose_weighted_mut - let mut items = [(0u32, 0i32); 14]; // (weight, count) - for (i, item) in items.iter_mut().enumerate() { - *item = (weights[i], 0); - } - for _ in 0..N_REPS { - items.choose_weighted_mut(&mut r, get_weight).unwrap().1 += 1; - } - for (ch, item) in chosen.iter_mut().zip(items.iter()) { - *ch = item.1; - } - verify(chosen); - - // Check error cases - let empty_slice = &mut [10][0..0]; - assert_eq!( - empty_slice.choose_weighted(&mut r, |_| 1), - Err(WeightError::InvalidInput) - ); - assert_eq!( - empty_slice.choose_weighted_mut(&mut r, |_| 1), - Err(WeightError::InvalidInput) - ); - assert_eq!( - ['x'].choose_weighted_mut(&mut r, |_| 0), - Err(WeightError::InsufficientNonZero) - ); - assert_eq!( - [0, -1].choose_weighted_mut(&mut r, |x| *x), - Err(WeightError::InvalidWeight) - ); - assert_eq!( - [-1, 0].choose_weighted_mut(&mut r, |x| *x), - Err(WeightError::InvalidWeight) - ); - } - - #[test] - fn value_stability_choose() { - fn choose>(iter: I) -> Option { - let mut rng = crate::test::rng(411); - iter.choose(&mut rng) - } - - assert_eq!(choose([].iter().cloned()), None); - assert_eq!(choose(0..100), Some(33)); - assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); - assert_eq!( - choose(ChunkHintedIterator { - iter: 0..100, - chunk_size: 32, - chunk_remaining: 32, - hint_total_size: false, - }), - Some(91) - ); - assert_eq!( - choose(ChunkHintedIterator { - iter: 0..100, - chunk_size: 32, - chunk_remaining: 32, - hint_total_size: true, - }), - Some(91) - ); - assert_eq!( - choose(WindowHintedIterator { - iter: 0..100, - window_size: 32, - hint_total_size: false, - }), - Some(34) - ); - assert_eq!( - choose(WindowHintedIterator { - iter: 0..100, - window_size: 32, - hint_total_size: true, - }), - Some(34) - ); - } - - #[test] - fn value_stability_choose_stable() { - fn choose>(iter: I) -> Option { - let mut rng = crate::test::rng(411); - iter.choose_stable(&mut rng) - } - - assert_eq!(choose([].iter().cloned()), None); - assert_eq!(choose(0..100), Some(27)); - assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); - assert_eq!( - choose(ChunkHintedIterator { - iter: 0..100, - chunk_size: 32, - chunk_remaining: 32, - hint_total_size: false, - }), - Some(27) - ); - assert_eq!( - choose(ChunkHintedIterator { - iter: 0..100, - chunk_size: 32, - chunk_remaining: 32, - hint_total_size: true, - }), - Some(27) - ); - assert_eq!( - choose(WindowHintedIterator { - iter: 0..100, - window_size: 32, - hint_total_size: false, - }), - Some(27) - ); - assert_eq!( - choose(WindowHintedIterator { - iter: 0..100, - window_size: 32, - hint_total_size: true, - }), - Some(27) - ); - } - - #[test] - fn value_stability_choose_multiple() { - fn do_test>(iter: I, v: &[u32]) { - let mut rng = crate::test::rng(412); - let mut buf = [0u32; 8]; - assert_eq!( - iter.clone().choose_multiple_fill(&mut rng, &mut buf), - v.len() - ); - assert_eq!(&buf[0..v.len()], v); - - #[cfg(feature = "alloc")] - { - let mut rng = crate::test::rng(412); - assert_eq!(iter.choose_multiple(&mut rng, v.len()), v); - } - } - - do_test(0..4, &[0, 1, 2, 3]); - do_test(0..8, &[0, 1, 2, 3, 4, 5, 6, 7]); - do_test(0..100, &[77, 95, 38, 23, 25, 8, 58, 40]); - } - - #[test] - #[cfg(feature = "std")] - fn test_multiple_weighted_edge_cases() { - use super::*; - - let mut rng = crate::test::rng(413); - - // Case 1: One of the weights is 0 - let choices = [('a', 2), ('b', 1), ('c', 0)]; - for _ in 0..100 { - let result = choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap() - .collect::>(); - - assert_eq!(result.len(), 2); - assert!(!result.iter().any(|val| val.0 == 'c')); - } - - // Case 2: All of the weights are 0 - let choices = [('a', 0), ('b', 0), ('c', 0)]; - let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); - assert_eq!(r.unwrap_err(), WeightError::InsufficientNonZero); - - // Case 3: Negative weights - let choices = [('a', -1), ('b', 1), ('c', 1)]; - let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); - assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); - - // Case 4: Empty list - let choices = []; - let r = choices.choose_multiple_weighted(&mut rng, 0, |_: &()| 0); - assert_eq!(r.unwrap().count(), 0); - - // Case 5: NaN weights - let choices = [('a', f64::NAN), ('b', 1.0), ('c', 1.0)]; - let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); - assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); - - // Case 6: +infinity weights - let choices = [('a', f64::INFINITY), ('b', 1.0), ('c', 1.0)]; - for _ in 0..100 { - let result = choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap() - .collect::>(); - assert_eq!(result.len(), 2); - assert!(result.iter().any(|val| val.0 == 'a')); - } - - // Case 7: -infinity weights - let choices = [('a', f64::NEG_INFINITY), ('b', 1.0), ('c', 1.0)]; - let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); - assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); - - // Case 8: -0 weights - let choices = [('a', -0.0), ('b', 1.0), ('c', 1.0)]; - let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); - assert!(r.is_ok()); - } - - #[test] - #[cfg(feature = "std")] - fn test_multiple_weighted_distributions() { - use super::*; - - // The theoretical probabilities of the different outcomes are: - // AB: 0.5 * 0.5 = 0.250 - // AC: 0.5 * 0.5 = 0.250 - // BA: 0.25 * 0.67 = 0.167 - // BC: 0.25 * 0.33 = 0.082 - // CA: 0.25 * 0.67 = 0.167 - // CB: 0.25 * 0.33 = 0.082 - let choices = [('a', 2), ('b', 1), ('c', 1)]; - let mut rng = crate::test::rng(414); - - let mut results = [0i32; 3]; - let expected_results = [4167, 4167, 1666]; - for _ in 0..10000 { - let result = choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap() - .collect::>(); - - assert_eq!(result.len(), 2); - - match (result[0].0, result[1].0) { - ('a', 'b') | ('b', 'a') => { - results[0] += 1; - } - ('a', 'c') | ('c', 'a') => { - results[1] += 1; - } - ('b', 'c') | ('c', 'b') => { - results[2] += 1; - } - (_, _) => panic!("unexpected result"), - } - } - - let mut diffs = results - .iter() - .zip(&expected_results) - .map(|(a, b)| (a - b).abs()); - assert!(!diffs.any(|deviation| deviation > 100)); - } -} diff --git a/src/seq/slice.rs b/src/seq/slice.rs new file mode 100644 index 00000000000..60a0b1e7e40 --- /dev/null +++ b/src/seq/slice.rs @@ -0,0 +1,765 @@ +// Copyright 2018-2023 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! `IndexedRandom`, `IndexedMutRandom`, `SliceRandom` + +use super::increasing_uniform::IncreasingUniform; +use super::{gen_index, index}; +#[cfg(feature = "alloc")] +use crate::distributions::uniform::{SampleBorrow, SampleUniform}; +#[cfg(feature = "alloc")] +use crate::distributions::{Weight, WeightError}; +use crate::Rng; +use core::ops::{Index, IndexMut}; + +/// Extension trait on indexable lists, providing random sampling methods. +/// +/// This trait is implemented on `[T]` slice types. Other types supporting +/// [`std::ops::Index`] may implement this (only [`Self::len`] must be +/// specified). +pub trait IndexedRandom: Index { + /// The length + fn len(&self) -> usize; + + /// True when the length is zero + #[inline] + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Uniformly sample one element + /// + /// Returns a reference to one uniformly-sampled random element of + /// the slice, or `None` if the slice is empty. + /// + /// For slices, complexity is `O(1)`. + /// + /// # Example + /// + /// ``` + /// use rand::thread_rng; + /// use rand::seq::IndexedRandom; + /// + /// let choices = [1, 2, 4, 8, 16, 32]; + /// let mut rng = thread_rng(); + /// println!("{:?}", choices.choose(&mut rng)); + /// assert_eq!(choices[..0].choose(&mut rng), None); + /// ``` + fn choose(&self, rng: &mut R) -> Option<&Self::Output> + where + R: Rng + ?Sized, + { + if self.is_empty() { + None + } else { + Some(&self[gen_index(rng, self.len())]) + } + } + + /// Uniformly sample `amount` distinct elements from self + /// + /// Chooses `amount` elements from the slice at random, without repetition, + /// and in random order. The returned iterator is appropriate both for + /// collection into a `Vec` and filling an existing buffer (see example). + /// + /// In case this API is not sufficiently flexible, use [`index::sample`]. + /// + /// For slices, complexity is the same as [`index::sample`]. + /// + /// # Example + /// ``` + /// use rand::seq::IndexedRandom; + /// + /// let mut rng = &mut rand::thread_rng(); + /// let sample = "Hello, audience!".as_bytes(); + /// + /// // collect the results into a vector: + /// let v: Vec = sample.choose_multiple(&mut rng, 3).cloned().collect(); + /// + /// // store in a buffer: + /// let mut buf = [0u8; 5]; + /// for (b, slot) in sample.choose_multiple(&mut rng, buf.len()).zip(buf.iter_mut()) { + /// *slot = *b; + /// } + /// ``` + #[cfg(feature = "alloc")] + fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter + where + Self::Output: Sized, + R: Rng + ?Sized, + { + let amount = core::cmp::min(amount, self.len()); + SliceChooseIter { + slice: self, + _phantom: Default::default(), + indices: index::sample(rng, self.len(), amount).into_iter(), + } + } + + /// Uniformly sample a fixed-size array of distinct elements from self + /// + /// Chooses `N` elements from the slice at random, without repetition, + /// and in random order. + /// + /// For slices, complexity is the same as [`index::sample_array`]. + /// + /// # Example + /// ``` + /// use rand::seq::IndexedRandom; + /// + /// let mut rng = &mut rand::thread_rng(); + /// let sample = "Hello, audience!".as_bytes(); + /// + /// let a: [u8; 3] = sample.choose_multiple_array(&mut rng).unwrap(); + /// ``` + fn choose_multiple_array(&self, rng: &mut R) -> Option<[Self::Output; N]> + where + Self::Output: Clone + Sized, + R: Rng + ?Sized, + { + let indices = index::sample_array(rng, self.len())?; + Some(indices.map(|index| self[index].clone())) + } + + /// Biased sampling for one element + /// + /// Returns a reference to one element of the slice, sampled according + /// to the provided weights. Returns `None` only if the slice is empty. + /// + /// The specified function `weight` maps each item `x` to a relative + /// likelihood `weight(x)`. The probability of each item being selected is + /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. + /// + /// For slices of length `n`, complexity is `O(n)`. + /// For more information about the underlying algorithm, + /// see [`distributions::WeightedIndex`]. + /// + /// See also [`choose_weighted_mut`]. + /// + /// # Example + /// + /// ``` + /// use rand::prelude::*; + /// + /// let choices = [('a', 2), ('b', 1), ('c', 1), ('d', 0)]; + /// let mut rng = thread_rng(); + /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c', + /// // and 'd' will never be printed + /// println!("{:?}", choices.choose_weighted(&mut rng, |item| item.1).unwrap().0); + /// ``` + /// [`choose`]: IndexedRandom::choose + /// [`choose_weighted_mut`]: IndexedMutRandom::choose_weighted_mut + /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex + #[cfg(feature = "alloc")] + fn choose_weighted( + &self, + rng: &mut R, + weight: F, + ) -> Result<&Self::Output, WeightError> + where + R: Rng + ?Sized, + F: Fn(&Self::Output) -> B, + B: SampleBorrow, + X: SampleUniform + Weight + PartialOrd, + { + use crate::distributions::{Distribution, WeightedIndex}; + let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; + Ok(&self[distr.sample(rng)]) + } + + /// Biased sampling of `amount` distinct elements + /// + /// Similar to [`choose_multiple`], but where the likelihood of each element's + /// inclusion in the output may be specified. The elements are returned in an + /// arbitrary, unspecified order. + /// + /// The specified function `weight` maps each item `x` to a relative + /// likelihood `weight(x)`. The probability of each item being selected is + /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. + /// + /// If all of the weights are equal, even if they are all zero, each element has + /// an equal likelihood of being selected. + /// + /// This implementation uses `O(length + amount)` space and `O(length)` time + /// if the "nightly" feature is enabled, or `O(length)` space and + /// `O(length + amount * log length)` time otherwise. + /// + /// # Example + /// + /// ``` + /// use rand::prelude::*; + /// + /// let choices = [('a', 2), ('b', 1), ('c', 1)]; + /// let mut rng = thread_rng(); + /// // First Draw * Second Draw = total odds + /// // ----------------------- + /// // (50% * 50%) + (25% * 67%) = 41.7% chance that the output is `['a', 'b']` in some order. + /// // (50% * 50%) + (25% * 67%) = 41.7% chance that the output is `['a', 'c']` in some order. + /// // (25% * 33%) + (25% * 33%) = 16.6% chance that the output is `['b', 'c']` in some order. + /// println!("{:?}", choices.choose_multiple_weighted(&mut rng, 2, |item| item.1).unwrap().collect::>()); + /// ``` + /// [`choose_multiple`]: IndexedRandom::choose_multiple + // Note: this is feature-gated on std due to usage of f64::powf. + // If necessary, we may use alloc+libm as an alternative (see PR #1089). + #[cfg(feature = "std")] + fn choose_multiple_weighted( + &self, + rng: &mut R, + amount: usize, + weight: F, + ) -> Result, WeightError> + where + Self::Output: Sized, + R: Rng + ?Sized, + F: Fn(&Self::Output) -> X, + X: Into, + { + let amount = core::cmp::min(amount, self.len()); + Ok(SliceChooseIter { + slice: self, + _phantom: Default::default(), + indices: index::sample_weighted( + rng, + self.len(), + |idx| weight(&self[idx]).into(), + amount, + )? + .into_iter(), + }) + } +} + +/// Extension trait on indexable lists, providing random sampling methods. +/// +/// This trait is implemented automatically for every type implementing +/// [`IndexedRandom`] and [`std::ops::IndexMut`]. +pub trait IndexedMutRandom: IndexedRandom + IndexMut { + /// Uniformly sample one element (mut) + /// + /// Returns a mutable reference to one uniformly-sampled random element of + /// the slice, or `None` if the slice is empty. + /// + /// For slices, complexity is `O(1)`. + fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Output> + where + R: Rng + ?Sized, + { + if self.is_empty() { + None + } else { + let len = self.len(); + Some(&mut self[gen_index(rng, len)]) + } + } + + /// Biased sampling for one element (mut) + /// + /// Returns a mutable reference to one element of the slice, sampled according + /// to the provided weights. Returns `None` only if the slice is empty. + /// + /// The specified function `weight` maps each item `x` to a relative + /// likelihood `weight(x)`. The probability of each item being selected is + /// therefore `weight(x) / s`, where `s` is the sum of all `weight(x)`. + /// + /// For slices of length `n`, complexity is `O(n)`. + /// For more information about the underlying algorithm, + /// see [`distributions::WeightedIndex`]. + /// + /// See also [`choose_weighted`]. + /// + /// [`choose_mut`]: IndexedMutRandom::choose_mut + /// [`choose_weighted`]: IndexedRandom::choose_weighted + /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex + #[cfg(feature = "alloc")] + fn choose_weighted_mut( + &mut self, + rng: &mut R, + weight: F, + ) -> Result<&mut Self::Output, WeightError> + where + R: Rng + ?Sized, + F: Fn(&Self::Output) -> B, + B: SampleBorrow, + X: SampleUniform + Weight + PartialOrd, + { + use crate::distributions::{Distribution, WeightedIndex}; + let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; + let index = distr.sample(rng); + Ok(&mut self[index]) + } +} + +/// Extension trait on slices, providing shuffling methods. +/// +/// This trait is implemented on all `[T]` slice types, providing several +/// methods for choosing and shuffling elements. You must `use` this trait: +/// +/// ``` +/// use rand::seq::SliceRandom; +/// +/// let mut rng = rand::thread_rng(); +/// let mut bytes = "Hello, random!".to_string().into_bytes(); +/// bytes.shuffle(&mut rng); +/// let str = String::from_utf8(bytes).unwrap(); +/// println!("{}", str); +/// ``` +/// Example output (non-deterministic): +/// ```none +/// l,nmroHado !le +/// ``` +pub trait SliceRandom: IndexedMutRandom { + /// Shuffle a mutable slice in place. + /// + /// For slices of length `n`, complexity is `O(n)`. + /// The resulting permutation is picked uniformly from the set of all possible permutations. + /// + /// # Example + /// + /// ``` + /// use rand::seq::SliceRandom; + /// use rand::thread_rng; + /// + /// let mut rng = thread_rng(); + /// let mut y = [1, 2, 3, 4, 5]; + /// println!("Unshuffled: {:?}", y); + /// y.shuffle(&mut rng); + /// println!("Shuffled: {:?}", y); + /// ``` + fn shuffle(&mut self, rng: &mut R) + where + R: Rng + ?Sized; + + /// Shuffle a slice in place, but exit early. + /// + /// Returns two mutable slices from the source slice. The first contains + /// `amount` elements randomly permuted. The second has the remaining + /// elements that are not fully shuffled. + /// + /// This is an efficient method to select `amount` elements at random from + /// the slice, provided the slice may be mutated. + /// + /// If you only need to choose elements randomly and `amount > self.len()/2` + /// then you may improve performance by taking + /// `amount = self.len() - amount` and using only the second slice. + /// + /// If `amount` is greater than the number of elements in the slice, this + /// will perform a full shuffle. + /// + /// For slices, complexity is `O(m)` where `m = amount`. + fn partial_shuffle( + &mut self, + rng: &mut R, + amount: usize, + ) -> (&mut [Self::Output], &mut [Self::Output]) + where + Self::Output: Sized, + R: Rng + ?Sized; +} + +impl IndexedRandom for [T] { + fn len(&self) -> usize { + self.len() + } +} + +impl + ?Sized> IndexedMutRandom for IR {} + +impl SliceRandom for [T] { + fn shuffle(&mut self, rng: &mut R) + where + R: Rng + ?Sized, + { + if self.len() <= 1 { + // There is no need to shuffle an empty or single element slice + return; + } + self.partial_shuffle(rng, self.len()); + } + + fn partial_shuffle(&mut self, rng: &mut R, amount: usize) -> (&mut [T], &mut [T]) + where + R: Rng + ?Sized, + { + let m = self.len().saturating_sub(amount); + + // The algorithm below is based on Durstenfeld's algorithm for the + // [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) + // for an unbiased permutation. + // It ensures that the last `amount` elements of the slice + // are randomly selected from the whole slice. + + // `IncreasingUniform::next_index()` is faster than `gen_index` + // but only works for 32 bit integers + // So we must use the slow method if the slice is longer than that. + if self.len() < (u32::MAX as usize) { + let mut chooser = IncreasingUniform::new(rng, m as u32); + for i in m..self.len() { + let index = chooser.next_index(); + self.swap(i, index); + } + } else { + for i in m..self.len() { + let index = gen_index(rng, i + 1); + self.swap(i, index); + } + } + let r = self.split_at_mut(m); + (r.1, r.0) + } +} + +/// An iterator over multiple slice elements. +/// +/// This struct is created by +/// [`IndexedRandom::choose_multiple`](trait.IndexedRandom.html#tymethod.choose_multiple). +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct SliceChooseIter<'a, S: ?Sized + 'a, T: 'a> { + slice: &'a S, + _phantom: core::marker::PhantomData, + indices: index::IndexVecIntoIter, +} + +#[cfg(feature = "alloc")] +impl<'a, S: Index + ?Sized + 'a, T: 'a> Iterator for SliceChooseIter<'a, S, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + // TODO: investigate using SliceIndex::get_unchecked when stable + self.indices.next().map(|i| &self.slice[i]) + } + + fn size_hint(&self) -> (usize, Option) { + (self.indices.len(), Some(self.indices.len())) + } +} + +#[cfg(feature = "alloc")] +impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator + for SliceChooseIter<'a, S, T> +{ + fn len(&self) -> usize { + self.indices.len() + } +} + +#[cfg(test)] +mod test { + use super::*; + #[cfg(feature = "alloc")] + use alloc::vec::Vec; + + #[test] + fn test_slice_choose() { + let mut r = crate::test::rng(107); + let chars = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + ]; + let mut chosen = [0i32; 14]; + // The below all use a binomial distribution with n=1000, p=1/14. + // binocdf(40, 1000, 1/14) ~= 2e-5; 1-binocdf(106, ..) ~= 2e-5 + for _ in 0..1000 { + let picked = *chars.choose(&mut r).unwrap(); + chosen[(picked as usize) - ('a' as usize)] += 1; + } + for count in chosen.iter() { + assert!(40 < *count && *count < 106); + } + + chosen.iter_mut().for_each(|x| *x = 0); + for _ in 0..1000 { + *chosen.choose_mut(&mut r).unwrap() += 1; + } + for count in chosen.iter() { + assert!(40 < *count && *count < 106); + } + + let mut v: [isize; 0] = []; + assert_eq!(v.choose(&mut r), None); + assert_eq!(v.choose_mut(&mut r), None); + } + + #[test] + fn value_stability_slice() { + let mut r = crate::test::rng(413); + let chars = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + ]; + let mut nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + + assert_eq!(chars.choose(&mut r), Some(&'l')); + assert_eq!(nums.choose_mut(&mut r), Some(&mut 3)); + + #[cfg(feature = "alloc")] + assert_eq!( + &chars + .choose_multiple(&mut r, 8) + .cloned() + .collect::>(), + &['f', 'i', 'd', 'b', 'c', 'm', 'j', 'k'] + ); + + #[cfg(feature = "alloc")] + assert_eq!(chars.choose_weighted(&mut r, |_| 1), Ok(&'l')); + #[cfg(feature = "alloc")] + assert_eq!(nums.choose_weighted_mut(&mut r, |_| 1), Ok(&mut 8)); + + let mut r = crate::test::rng(414); + nums.shuffle(&mut r); + assert_eq!(nums, [5, 11, 0, 8, 7, 12, 6, 4, 9, 3, 1, 2, 10]); + nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + let res = nums.partial_shuffle(&mut r, 6); + assert_eq!(res.0, &mut [7, 12, 6, 8, 1, 9]); + assert_eq!(res.1, &mut [0, 11, 2, 3, 4, 5, 10]); + } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_shuffle() { + let mut r = crate::test::rng(108); + let empty: &mut [isize] = &mut []; + empty.shuffle(&mut r); + let mut one = [1]; + one.shuffle(&mut r); + let b: &[_] = &[1]; + assert_eq!(one, b); + + let mut two = [1, 2]; + two.shuffle(&mut r); + assert!(two == [1, 2] || two == [2, 1]); + + fn move_last(slice: &mut [usize], pos: usize) { + // use slice[pos..].rotate_left(1); once we can use that + let last_val = slice[pos]; + for i in pos..slice.len() - 1 { + slice[i] = slice[i + 1]; + } + *slice.last_mut().unwrap() = last_val; + } + let mut counts = [0i32; 24]; + for _ in 0..10000 { + let mut arr: [usize; 4] = [0, 1, 2, 3]; + arr.shuffle(&mut r); + let mut permutation = 0usize; + let mut pos_value = counts.len(); + for i in 0..4 { + pos_value /= 4 - i; + let pos = arr.iter().position(|&x| x == i).unwrap(); + assert!(pos < (4 - i)); + permutation += pos * pos_value; + move_last(&mut arr, pos); + assert_eq!(arr[3], i); + } + for (i, &a) in arr.iter().enumerate() { + assert_eq!(a, i); + } + counts[permutation] += 1; + } + for count in counts.iter() { + // Binomial(10000, 1/24) with average 416.667 + // Octave: binocdf(n, 10000, 1/24) + // 99.9% chance samples lie within this range: + assert!(352 <= *count && *count <= 483, "count: {}", count); + } + } + + #[test] + fn test_partial_shuffle() { + let mut r = crate::test::rng(118); + + let mut empty: [u32; 0] = []; + let res = empty.partial_shuffle(&mut r, 10); + assert_eq!((res.0.len(), res.1.len()), (0, 0)); + + let mut v = [1, 2, 3, 4, 5]; + let res = v.partial_shuffle(&mut r, 2); + assert_eq!((res.0.len(), res.1.len()), (2, 3)); + assert!(res.0[0] != res.0[1]); + // First elements are only modified if selected, so at least one isn't modified: + assert!(res.1[0] == 1 || res.1[1] == 2 || res.1[2] == 3); + } + + #[test] + #[cfg(feature = "alloc")] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_weighted() { + let mut r = crate::test::rng(406); + const N_REPS: u32 = 3000; + let weights = [1u32, 2, 3, 0, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]; + let total_weight = weights.iter().sum::() as f32; + + let verify = |result: [i32; 14]| { + for (i, count) in result.iter().enumerate() { + let exp = (weights[i] * N_REPS) as f32 / total_weight; + let mut err = (*count as f32 - exp).abs(); + if err != 0.0 { + err /= exp; + } + assert!(err <= 0.25); + } + }; + + // choose_weighted + fn get_weight(item: &(u32, T)) -> u32 { + item.0 + } + let mut chosen = [0i32; 14]; + let mut items = [(0u32, 0usize); 14]; // (weight, index) + for (i, item) in items.iter_mut().enumerate() { + *item = (weights[i], i); + } + for _ in 0..N_REPS { + let item = items.choose_weighted(&mut r, get_weight).unwrap(); + chosen[item.1] += 1; + } + verify(chosen); + + // choose_weighted_mut + let mut items = [(0u32, 0i32); 14]; // (weight, count) + for (i, item) in items.iter_mut().enumerate() { + *item = (weights[i], 0); + } + for _ in 0..N_REPS { + items.choose_weighted_mut(&mut r, get_weight).unwrap().1 += 1; + } + for (ch, item) in chosen.iter_mut().zip(items.iter()) { + *ch = item.1; + } + verify(chosen); + + // Check error cases + let empty_slice = &mut [10][0..0]; + assert_eq!( + empty_slice.choose_weighted(&mut r, |_| 1), + Err(WeightError::InvalidInput) + ); + assert_eq!( + empty_slice.choose_weighted_mut(&mut r, |_| 1), + Err(WeightError::InvalidInput) + ); + assert_eq!( + ['x'].choose_weighted_mut(&mut r, |_| 0), + Err(WeightError::InsufficientNonZero) + ); + assert_eq!( + [0, -1].choose_weighted_mut(&mut r, |x| *x), + Err(WeightError::InvalidWeight) + ); + assert_eq!( + [-1, 0].choose_weighted_mut(&mut r, |x| *x), + Err(WeightError::InvalidWeight) + ); + } + + #[test] + #[cfg(feature = "std")] + fn test_multiple_weighted_edge_cases() { + use super::*; + + let mut rng = crate::test::rng(413); + + // Case 1: One of the weights is 0 + let choices = [('a', 2), ('b', 1), ('c', 0)]; + for _ in 0..100 { + let result = choices + .choose_multiple_weighted(&mut rng, 2, |item| item.1) + .unwrap() + .collect::>(); + + assert_eq!(result.len(), 2); + assert!(!result.iter().any(|val| val.0 == 'c')); + } + + // Case 2: All of the weights are 0 + let choices = [('a', 0), ('b', 0), ('c', 0)]; + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert_eq!(r.unwrap_err(), WeightError::InsufficientNonZero); + + // Case 3: Negative weights + let choices = [('a', -1), ('b', 1), ('c', 1)]; + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); + + // Case 4: Empty list + let choices = []; + let r = choices.choose_multiple_weighted(&mut rng, 0, |_: &()| 0); + assert_eq!(r.unwrap().count(), 0); + + // Case 5: NaN weights + let choices = [('a', f64::NAN), ('b', 1.0), ('c', 1.0)]; + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); + + // Case 6: +infinity weights + let choices = [('a', f64::INFINITY), ('b', 1.0), ('c', 1.0)]; + for _ in 0..100 { + let result = choices + .choose_multiple_weighted(&mut rng, 2, |item| item.1) + .unwrap() + .collect::>(); + assert_eq!(result.len(), 2); + assert!(result.iter().any(|val| val.0 == 'a')); + } + + // Case 7: -infinity weights + let choices = [('a', f64::NEG_INFINITY), ('b', 1.0), ('c', 1.0)]; + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert_eq!(r.unwrap_err(), WeightError::InvalidWeight); + + // Case 8: -0 weights + let choices = [('a', -0.0), ('b', 1.0), ('c', 1.0)]; + let r = choices.choose_multiple_weighted(&mut rng, 2, |item| item.1); + assert!(r.is_ok()); + } + + #[test] + #[cfg(feature = "std")] + fn test_multiple_weighted_distributions() { + use super::*; + + // The theoretical probabilities of the different outcomes are: + // AB: 0.5 * 0.5 = 0.250 + // AC: 0.5 * 0.5 = 0.250 + // BA: 0.25 * 0.67 = 0.167 + // BC: 0.25 * 0.33 = 0.082 + // CA: 0.25 * 0.67 = 0.167 + // CB: 0.25 * 0.33 = 0.082 + let choices = [('a', 2), ('b', 1), ('c', 1)]; + let mut rng = crate::test::rng(414); + + let mut results = [0i32; 3]; + let expected_results = [4167, 4167, 1666]; + for _ in 0..10000 { + let result = choices + .choose_multiple_weighted(&mut rng, 2, |item| item.1) + .unwrap() + .collect::>(); + + assert_eq!(result.len(), 2); + + match (result[0].0, result[1].0) { + ('a', 'b') | ('b', 'a') => { + results[0] += 1; + } + ('a', 'c') | ('c', 'a') => { + results[1] += 1; + } + ('b', 'c') | ('c', 'b') => { + results[2] += 1; + } + (_, _) => panic!("unexpected result"), + } + } + + let mut diffs = results + .iter() + .zip(&expected_results) + .map(|(a, b)| (a - b).abs()); + assert!(!diffs.any(|deviation| deviation > 100)); + } +} From 17746a1aced109609637f22f0285ab664a98e8da Mon Sep 17 00:00:00 2001 From: Michael Dyer <59163924+MichaelOwenDyer@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:16:19 +0200 Subject: [PATCH 391/443] Add distribution plots to `rand_distr` documentation (#1434) --- rand_distr/CHANGELOG.md | 5 ++ rand_distr/Cargo.toml | 2 +- rand_distr/src/binomial.rs | 19 +++- rand_distr/src/cauchy.rs | 34 +++++-- rand_distr/src/dirichlet.rs | 21 ++++- rand_distr/src/exponential.rs | 60 +++++++++---- rand_distr/src/frechet.rs | 26 ++++-- rand_distr/src/gamma.rs | 105 +++++++++++++++++----- rand_distr/src/geometric.rs | 44 ++++++--- rand_distr/src/gumbel.rs | 24 +++-- rand_distr/src/hypergeometric.rs | 23 +++-- rand_distr/src/inverse_gaussian.rs | 24 ++++- rand_distr/src/normal.rs | 76 ++++++++++++---- rand_distr/src/normal_inverse_gaussian.rs | 21 ++++- rand_distr/src/pareto.rs | 16 +++- rand_distr/src/pert.rs | 9 +- rand_distr/src/poisson.rs | 19 +++- rand_distr/src/skew_normal.rs | 22 ++++- rand_distr/src/triangular.rs | 9 +- rand_distr/src/unit_ball.rs | 15 +++- rand_distr/src/unit_circle.rs | 12 ++- rand_distr/src/unit_disc.rs | 11 +++ rand_distr/src/unit_sphere.rs | 12 +++ rand_distr/src/weibull.rs | 14 ++- rand_distr/src/zipf.rs | 48 ++++++---- src/distributions/bernoulli.rs | 15 +++- src/distributions/weighted_index.rs | 2 +- 27 files changed, 548 insertions(+), 140 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index e29c59910df..cab7591505d 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added +- Add plots for `rand_distr` distributions to documentation (#1434) + ## [0.5.0-alpha.1] - 2024-03-18 - Target `rand` version `0.9.0-alpha.1` diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 5ca2f3d93b0..4f21c62b132 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["random", "rng", "distribution", "probability"] categories = ["algorithms", "no-std"] edition = "2021" rust-version = "1.61" -include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] +include = ["/src", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] rustdoc-args = ["--cfg docsrs", "--generate-link-to-definition"] diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 3623561e78b..514dbeca904 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -7,7 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The binomial distribution. +//! The binomial distribution `Binomial(n, p)`. use crate::{Distribution, Uniform}; use core::cmp::Ordering; @@ -16,11 +16,24 @@ use core::fmt; use num_traits::Float; use rand::Rng; -/// The binomial distribution `Binomial(n, p)`. +/// The [binomial distribution](https://en.wikipedia.org/wiki/Binomial_distribution) `Binomial(n, p)`. +/// +/// The binomial distribution is a discrete probability distribution +/// which describes the probability of seeing `k` successes in `n` +/// independent trials, each of which has success probability `p`. +/// +/// # Density function /// -/// This distribution has density function: /// `f(k) = n!/(k! (n-k)!) p^k (1-p)^(n-k)` for `k >= 0`. /// +/// # Plot +/// +/// The following plot of the binomial distribution illustrates the +/// probability of `k` successes out of `n = 10` trials with `p = 0.2` +/// and `p = 0.6` for `0 <= k <= n`. +/// +/// ![Binomial distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/binomial.svg) +/// /// # Example /// /// ``` diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 268b5492a98..6d4ff4ec18d 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -7,20 +7,37 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Cauchy distribution. +//! The Cauchy distribution `Cauchy(x₀, γ)`. use crate::{Distribution, Standard}; use core::fmt; use num_traits::{Float, FloatConst}; use rand::Rng; -/// The Cauchy distribution `Cauchy(median, scale)`. +/// The [Cauchy distribution](https://en.wikipedia.org/wiki/Cauchy_distribution) `Cauchy(x₀, γ)`. /// -/// This distribution has a density function: -/// `f(x) = 1 / (pi * scale * (1 + ((x - median) / scale)^2))` +/// The Cauchy distribution is a continuous probability distribution with +/// parameters `x₀` (median) and `γ` (scale). +/// It describes the distribution of the ratio of two independent +/// normally distributed random variables with means `x₀` and scales `γ`. +/// In other words, if `X` and `Y` are independent normally distributed +/// random variables with means `x₀` and scales `γ`, respectively, then +/// `X / Y` is `Cauchy(x₀, γ)` distributed. /// -/// Note that at least for `f32`, results are not fully portable due to minor -/// differences in the target system's *tan* implementation, `tanf`. +/// # Density function +/// +/// `f(x) = 1 / (π * γ * (1 + ((x - x₀) / γ)²))` +/// +/// # Plot +/// +/// The plot illustrates the Cauchy distribution with various values of `x₀` and `γ`. +/// Note how the median parameter `x₀` shifts the distribution along the x-axis, +/// and how the scale `γ` changes the density around the median. +/// +/// The standard Cauchy distribution is the special case with `x₀ = 0` and `γ = 1`, +/// which corresponds to the ratio of two [`StandardNormal`](crate::StandardNormal) distributions. +/// +/// ![Cauchy distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/cauchy.svg) /// /// # Example /// @@ -31,6 +48,11 @@ use rand::Rng; /// let v = cau.sample(&mut rand::thread_rng()); /// println!("{} is from a Cauchy(2, 5) distribution", v); /// ``` +/// +/// # Notes +/// +/// Note that at least for `f32`, results are not fully portable due to minor +/// differences in the target system's *tan* implementation, `tanf`. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Cauchy diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index ca192c01948..9605308432c 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -7,7 +7,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The dirichlet distribution. +//! The dirichlet distribution `Dirichlet(α₁, α₂, ..., αₙ)`. + #![cfg(feature = "alloc")] use crate::{Beta, Distribution, Exp1, Gamma, Open01, StandardNormal}; use core::fmt; @@ -185,11 +186,23 @@ where FromBeta(DirichletFromBeta), } -/// The Dirichlet distribution `Dirichlet(alpha)`. +/// The [Dirichlet distribution](https://en.wikipedia.org/wiki/Dirichlet_distribution) `Dirichlet(α₁, α₂, ..., αₖ)`. /// /// The Dirichlet distribution is a family of continuous multivariate -/// probability distributions parameterized by a vector alpha of positive reals. -/// It is a multivariate generalization of the beta distribution. +/// probability distributions parameterized by a vector of positive +/// real numbers `α₁, α₂, ..., αₖ`, where `k` is the number of dimensions +/// of the distribution. The distribution is supported on the `k-1`-dimensional +/// simplex, which is the set of points `x = [x₁, x₂, ..., xₖ]` such that +/// `0 ≤ xᵢ ≤ 1` and `∑ xᵢ = 1`. +/// It is a multivariate generalization of the [`Beta`](crate::Beta) distribution. +/// The distribution is symmetric when all `αᵢ` are equal. +/// +/// # Plot +/// +/// The following plot illustrates the 2-dimensional simplices for various +/// 3-dimensional Dirichlet distributions. +/// +/// ![Dirichlet distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/dirichlet.png) /// /// # Example /// diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index 88f60d236c9..4c919b20960 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -7,7 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The exponential distribution. +//! The exponential distribution `Exp(λ)`. use crate::utils::ziggurat; use crate::{ziggurat_tables, Distribution}; @@ -15,22 +15,21 @@ use core::fmt; use num_traits::Float; use rand::Rng; -/// Samples floating-point numbers according to the exponential distribution, -/// with rate parameter `λ = 1`. This is equivalent to `Exp::new(1.0)` or -/// sampling with `-rng.gen::().ln()`, but faster. +/// The standard exponential distribution `Exp(1)`. /// -/// See `Exp` for the general exponential distribution. +/// This is equivalent to `Exp::new(1.0)` or sampling with +/// `-rng.gen::().ln()`, but faster. /// -/// Implemented via the ZIGNOR variant[^1] of the Ziggurat method. The exact -/// description in the paper was adjusted to use tables for the exponential -/// distribution rather than normal. +/// See [`Exp`](crate::Exp) for the general exponential distribution. /// -/// [^1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to -/// Generate Normal Random Samples*]( -/// https://www.doornik.com/research/ziggurat.pdf). -/// Nuffield College, Oxford +/// # Plot +/// +/// The following plot illustrates the exponential distribution with `λ = 1`. +/// +/// ![Exponential distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/exponential_exp1.svg) /// /// # Example +/// /// ``` /// use rand::prelude::*; /// use rand_distr::Exp1; @@ -38,6 +37,17 @@ use rand::Rng; /// let val: f64 = thread_rng().sample(Exp1); /// println!("{}", val); /// ``` +/// +/// # Notes +/// +/// Implemented via the ZIGNOR variant[^1] of the Ziggurat method. The exact +/// description in the paper was adjusted to use tables for the exponential +/// distribution rather than normal. +/// +/// [^1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to +/// Generate Normal Random Samples*]( +/// https://www.doornik.com/research/ziggurat.pdf). +/// Nuffield College, Oxford #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Exp1; @@ -75,12 +85,30 @@ impl Distribution for Exp1 { } } -/// The exponential distribution `Exp(lambda)`. +/// The [exponential distribution](https://en.wikipedia.org/wiki/Exponential_distribution) `Exp(λ)`. +/// +/// The exponential distribution is a continuous probability distribution +/// with rate parameter `λ` (`lambda`). It describes the time between events +/// in a [`Poisson`](crate::Poisson) process, i.e. a process in which +/// events occur continuously and independently at a constant average rate. +/// +/// See [`Exp1`](crate::Exp1) for an optimised implementation for `λ = 1`. +/// +/// # Density function +/// +/// `f(x) = λ * exp(-λ * x)` for `x > 0`, when `λ > 0`. +/// +/// For `λ = 0`, all samples yield infinity (because a Poisson process +/// with rate 0 has no events). +/// +/// # Plot /// -/// This distribution has density function: `f(x) = lambda * exp(-lambda * x)` -/// for `x > 0`, when `lambda > 0`. For `lambda = 0`, all samples yield infinity. +/// The following plot illustrates the exponential distribution with +/// various values of `λ`. +/// The `λ` parameter controls the rate of decay as `x` approaches infinity, +/// and the mean of the distribution is `1/λ`. /// -/// Note that [`Exp1`](crate::Exp1) is an optimised implementation for `lambda = 1`. +/// ![Exponential distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/exponential.svg) /// /// # Example /// diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index 22d7b62e848..b274946d66e 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -6,20 +6,36 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Fréchet distribution. +//! The Fréchet distribution `Fréchet(μ, σ, α)`. use crate::{Distribution, OpenClosed01}; use core::fmt; use num_traits::Float; use rand::Rng; -/// Samples floating-point numbers according to the Fréchet distribution +/// The [Fréchet distribution](https://en.wikipedia.org/wiki/Fr%C3%A9chet_distribution) `Fréchet(α, μ, σ)`. /// -/// This distribution has density function: -/// `f(x) = [(x - μ) / σ]^(-1 - α) exp[-(x - μ) / σ]^(-α) α / σ`, -/// where `μ` is the location parameter, `σ` the scale parameter, and `α` the shape parameter. +/// The Fréchet distribution is a continuous probability distribution +/// with location parameter `μ` (`mu`), scale parameter `σ` (`sigma`), +/// and shape parameter `α` (`alpha`). It describes the distribution +/// of the maximum (or minimum) of a number of random variables. +/// It is also known as the Type II extreme value distribution. +/// +/// # Density function +/// +/// `f(x) = [(x - μ) / σ]^(-1 - α) exp[-(x - μ) / σ]^(-α) α / σ` +/// +/// # Plot +/// +/// The plot shows the Fréchet distribution with various values of `μ`, `σ`, and `α`. +/// Note how the location parameter `μ` shifts the distribution along the x-axis, +/// the scale parameter `σ` stretches or compresses the distribution along the x-axis, +/// and the shape parameter `α` changes the tail behavior. +/// +/// ![Fréchet distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/frechet.svg) /// /// # Example +/// /// ``` /// use rand::prelude::*; /// use rand_distr::Frechet; diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 8b3a39205a6..23051e45d33 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -24,21 +24,28 @@ use rand::Rng; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -/// The Gamma distribution `Gamma(shape, scale)` distribution. +/// The [Gamma distribution](https://en.wikipedia.org/wiki/Gamma_distribution) `Gamma(k, θ)`. /// -/// The density function of this distribution is +/// The Gamma distribution is a continuous probability distribution +/// with shape parameter `k > 0` (number of events) and +/// scale parameter `θ > 0` (mean waiting time between events). +/// It describes the time until `k` events occur in a Poisson +/// process with rate `1/θ`. It is the generalization of the +/// [`Exponential`](crate::Exp) distribution. /// -/// ```text -/// f(x) = x^(k - 1) * exp(-x / θ) / (Γ(k) * θ^k) -/// ``` +/// # Density function /// -/// where `Γ` is the Gamma function, `k` is the shape and `θ` is the -/// scale and both `k` and `θ` are strictly positive. +/// `f(x) = x^(k - 1) * exp(-x / θ) / (Γ(k) * θ^k)` for `x > 0`, +/// where `Γ` is the [gamma function](https://en.wikipedia.org/wiki/Gamma_function). /// -/// The algorithm used is that described by Marsaglia & Tsang 2000[^1], -/// falling back to directly sampling from an Exponential for `shape -/// == 1`, and using the boosting technique described in that paper for -/// `shape < 1`. +/// # Plot +/// +/// The following plot illustrates the Gamma distribution with +/// various values of `k` and `θ`. +/// Curves with `θ = 1` are more saturated, while corresponding +/// curves with `θ = 2` have a lighter color. +/// +/// ![Gamma distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/gamma.svg) /// /// # Example /// @@ -50,6 +57,13 @@ use serde::{Deserialize, Serialize}; /// println!("{} is from a Gamma(2, 5) distribution", v); /// ``` /// +/// # Notes +/// +/// The algorithm used is that described by Marsaglia & Tsang 2000[^1], +/// falling back to directly sampling from an Exponential for `shape +/// == 1`, and using the boosting technique described in that paper for +/// `shape < 1`. +/// /// [^1]: George Marsaglia and Wai Wan Tsang. 2000. "A Simple Method for /// Generating Gamma Variables" *ACM Trans. Math. Softw.* 26, 3 /// (September 2000), 363-372. @@ -262,14 +276,23 @@ where } } -/// The chi-squared distribution `χ²(k)`, where `k` is the degrees of -/// freedom. +/// The [chi-squared distribution](https://en.wikipedia.org/wiki/Chi-squared_distribution) `χ²(k)`. +/// +/// The chi-squared distribution is a continuous probability +/// distribution with parameter `k > 0` degrees of freedom. /// /// For `k > 0` integral, this distribution is the sum of the squares /// of `k` independent standard normal random variables. For other /// `k`, this uses the equivalent characterisation /// `χ²(k) = Gamma(k/2, 2)`. /// +/// # Plot +/// +/// The plot shows the chi-squared distribution with various degrees +/// of freedom. +/// +/// ![Chi-squared distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/chi_squared.svg) +/// /// # Example /// /// ``` @@ -368,12 +391,18 @@ where } } -/// The Fisher F distribution `F(m, n)`. +/// The [Fisher F-distribution](https://en.wikipedia.org/wiki/F-distribution) `F(m, n)`. /// /// This distribution is equivalent to the ratio of two normalised /// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) / /// (χ²(n)/n)`. /// +/// # Plot +/// +/// The plot shows the F-distribution with various values of `m` and `n`. +/// +/// ![F-distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/fisher_f.svg) +/// /// # Example /// /// ``` @@ -457,8 +486,25 @@ where } } -/// The Student t distribution, `t(nu)`, where `nu` is the degrees of -/// freedom. +/// The [Student t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) `t(ν)`. +/// +/// The t-distribution is a continuous probability distribution +/// parameterized by degrees of freedom `ν` (`nu`), which +/// arises when estimating the mean of a normally-distributed +/// population in situations where the sample size is small and +/// the population's standard deviation is unknown. +/// It is widely used in hypothesis testing. +/// +/// For `ν = 1`, this is equivalent to the standard +/// [`Cauchy`](crate::Cauchy) distribution, +/// and as `ν` diverges to infinity, `t(ν)` converges to +/// [`StandardNormal`](crate::StandardNormal). +/// +/// # Plot +/// +/// The plot shows the t-distribution with various degrees of freedom. +/// +/// ![T-distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/student_t.svg) /// /// # Example /// @@ -489,12 +535,12 @@ where Exp1: Distribution, Open01: Distribution, { - /// Create a new Student t distribution with `n` degrees of - /// freedom. - pub fn new(n: F) -> Result, ChiSquaredError> { + /// Create a new Student t-distribution with `ν` (nu) + /// degrees of freedom. + pub fn new(nu: F) -> Result, ChiSquaredError> { Ok(StudentT { - chi: ChiSquared::new(n)?, - dof: n, + chi: ChiSquared::new(nu)?, + dof: nu, }) } } @@ -545,7 +591,22 @@ struct BC { kappa2: N, } -/// The Beta distribution with shape parameters `alpha` and `beta`. +/// The [Beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) `Beta(α, β)`. +/// +/// The Beta distribution is a continuous probability distribution +/// defined on the interval `[0, 1]`. It is the conjugate prior for the +/// parameter `p` of the [`Binomial`][crate::Binomial] distribution. +/// +/// It has two shape parameters `α` (alpha) and `β` (beta) which control +/// the shape of the distribution. Both `a` and `β` must be greater than zero. +/// The distribution is symmetric when `α = β`. +/// +/// # Plot +/// +/// The plot shows the Beta distribution with various combinations +/// of `α` and `β`. +/// +/// ![Beta distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/beta.svg) /// /// # Example /// diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index e4bef5cf52f..e54496d8e5d 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -1,4 +1,4 @@ -//! The geometric distribution. +//! The geometric distribution `Geometric(p)`. use crate::Distribution; use core::fmt; @@ -6,20 +6,31 @@ use core::fmt; use num_traits::Float; use rand::Rng; -/// The geometric distribution `Geometric(p)` bounded to `[0, u64::MAX]`. +/// The [geometric distribution](https://en.wikipedia.org/wiki/Geometric_distribution) `Geometric(p)`. /// -/// This is the probability distribution of the number of failures before the -/// first success in a series of Bernoulli trials. It has the density function -/// `f(k) = (1 - p)^k p` for `k >= 0`, where `p` is the probability of success -/// on each trial. +/// This is the probability distribution of the number of failures +/// (bounded to `[0, u64::MAX]`) before the first success in a +/// series of [`Bernoulli`](crate::Bernoulli) trials, where the +/// probability of success on each trial is `p`. /// /// This is the discrete analogue of the [exponential distribution](crate::Exp). /// -/// Note that [`StandardGeometric`](crate::StandardGeometric) is an optimised +/// See [`StandardGeometric`](crate::StandardGeometric) for an optimised /// implementation for `p = 0.5`. /// -/// # Example +/// # Density function +/// +/// `f(k) = (1 - p)^k p` for `k >= 0`. +/// +/// # Plot +/// +/// The following plot illustrates the geometric distribution for various +/// values of `p`. Note how higher `p` values shift the distribution to +/// the left, and the mean of the distribution is `1/p`. /// +/// ![Geometric distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/geometric.svg) +/// +/// # Example /// ``` /// use rand_distr::{Geometric, Distribution}; /// @@ -140,14 +151,17 @@ impl Distribution for Geometric { } } -/// Samples integers according to the geometric distribution with success -/// probability `p = 0.5`. This is equivalent to `Geometeric::new(0.5)`, -/// but faster. +/// The standard geometric distribution `Geometric(0.5)`. +/// +/// This is equivalent to `Geometric::new(0.5)`, but faster. /// /// See [`Geometric`](crate::Geometric) for the general geometric distribution. /// -/// Implemented via iterated -/// [`Rng::gen::().leading_zeros()`](Rng::gen::().leading_zeros()). +/// # Plot +/// +/// The following plot illustrates the standard geometric distribution. +/// +/// ![Standard Geometric distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/standard_geometric.svg) /// /// # Example /// ``` @@ -157,6 +171,10 @@ impl Distribution for Geometric { /// let v = StandardGeometric.sample(&mut thread_rng()); /// println!("{} is from a Geometric(0.5) distribution", v); /// ``` +/// +/// # Notes +/// Implemented via iterated +/// [`Rng::gen::().leading_zeros()`](Rng::gen::().leading_zeros()). #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct StandardGeometric; diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 3b2e79b9f35..fd9324acf2b 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -6,18 +6,32 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Gumbel distribution. +//! The Gumbel distribution `Gumbel(μ, β)`. use crate::{Distribution, OpenClosed01}; use core::fmt; use num_traits::Float; use rand::Rng; -/// Samples floating-point numbers according to the Gumbel distribution +/// The [Gumbel distribution](https://en.wikipedia.org/wiki/Gumbel_distribution) `Gumbel(μ, β)`. /// -/// This distribution has density function: -/// `f(x) = exp(-(z + exp(-z))) / σ`, where `z = (x - μ) / σ`, -/// `μ` is the location parameter, and `σ` the scale parameter. +/// The Gumbel distribution is a continuous probability distribution +/// with location parameter `μ` (`mu`) and scale parameter `β` (`beta`). +/// It is used to model the distribution of the maximum (or minimum) +/// of a number of samples of various distributions. +/// +/// # Density function +/// +/// `f(x) = exp(-(z + exp(-z))) / β`, where `z = (x - μ) / β`. +/// +/// # Plot +/// +/// The following plot illustrates the Gumbel distribution with various values of `μ` and `β`. +/// Note how the location parameter `μ` shifts the distribution along the x-axis, +/// and the scale parameter `β` changes the density around `μ`. +/// Note also the asymptotic behavior of the distribution towards the right. +/// +/// ![Gumbel distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/gumbel.svg) /// /// # Example /// ``` diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index f9ffda25725..c15b143b626 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -1,4 +1,4 @@ -//! The hypergeometric distribution. +//! The hypergeometric distribution `Hypergeometric(N, K, n)`. use crate::Distribution; use core::fmt; @@ -27,20 +27,29 @@ enum SamplingMethod { }, } -/// The hypergeometric distribution `Hypergeometric(N, K, n)`. +/// The [hypergeometric distribution](https://en.wikipedia.org/wiki/Hypergeometric_distribution) `Hypergeometric(N, K, n)`. /// /// This is the distribution of successes in samples of size `n` drawn without /// replacement from a population of size `N` containing `K` success states. -/// It has the density function: -/// `f(k) = binomial(K, k) * binomial(N-K, n-k) / binomial(N, n)`, -/// where `binomial(a, b) = a! / (b! * (a - b)!)`. /// -/// The [binomial distribution](crate::Binomial) is the analogous distribution +/// See the [binomial distribution](crate::Binomial) for the analogous distribution /// for sampling with replacement. It is a good approximation when the population /// size is much larger than the sample size. /// -/// # Example +/// # Density function +/// +/// `f(k) = binomial(K, k) * binomial(N-K, n-k) / binomial(N, n)`, +/// where `binomial(a, b) = a! / (b! * (a - b)!)`. +/// +/// # Plot +/// +/// The following plot of the hypergeometric distribution illustrates the probability of drawing +/// `k` successes in `n = 10` draws from a population of `N = 50` items, of which either `K = 12` +/// or `K = 35` are successes. /// +/// ![Hypergeometric distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/hypergeometric.svg) +/// +/// # Example /// ``` /// use rand_distr::{Distribution, Hypergeometric}; /// diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 39e53eb0f74..1039f6045ba 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -1,3 +1,5 @@ +//! The inverse Gaussian distribution `IG(μ, λ)`. + use crate::{Distribution, Standard, StandardNormal}; use core::fmt; use num_traits::Float; @@ -24,7 +26,27 @@ impl fmt::Display for Error { #[cfg(feature = "std")] impl std::error::Error for Error {} -/// The [inverse Gaussian distribution](https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution) +/// The [inverse Gaussian distribution](https://en.wikipedia.org/wiki/Inverse_Gaussian_distribution) `IG(μ, λ)`. +/// +/// This is a continuous probability distribution with mean parameter `μ` (`mu`) +/// and shape parameter `λ` (`lambda`), defined for `x > 0`. +/// It is also known as the Wald distribution. +/// +/// # Plot +/// +/// The following plot shows the inverse Gaussian distribution +/// with various values of `μ` and `λ`. +/// +/// ![Inverse Gaussian distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/inverse_gaussian.svg) +/// +/// # Example +/// ``` +/// use rand_distr::{InverseGaussian, Distribution}; +/// +/// let inv_gauss = InverseGaussian::new(1.0, 2.0).unwrap(); +/// let v = inv_gauss.sample(&mut rand::thread_rng()); +/// println!("{} is from a inverse Gaussian(1, 2) distribution", v); +/// ``` #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct InverseGaussian diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 1d427514eca..1b698ec4bb8 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -7,7 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The normal and derived distributions. +//! The Normal and derived distributions. use crate::utils::ziggurat; use crate::{ziggurat_tables, Distribution, Open01}; @@ -15,18 +15,17 @@ use core::fmt; use num_traits::Float; use rand::Rng; -/// Samples floating-point numbers according to the normal distribution -/// `N(0, 1)` (a.k.a. a standard normal, or Gaussian). This is equivalent to -/// `Normal::new(0.0, 1.0)` but faster. +/// The standard Normal distribution `N(0, 1)`. /// -/// See `Normal` for the general normal distribution. +/// This is equivalent to `Normal::new(0.0, 1.0)`, but faster. /// -/// Implemented via the ZIGNOR variant[^1] of the Ziggurat method. +/// See [`Normal`](crate::Normal) for the general Normal distribution. /// -/// [^1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to -/// Generate Normal Random Samples*]( -/// https://www.doornik.com/research/ziggurat.pdf). -/// Nuffield College, Oxford +/// # Plot +/// +/// The following diagram shows the standard Normal distribution. +/// +/// ![Standard Normal distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/standard_normal.svg) /// /// # Example /// ``` @@ -36,6 +35,15 @@ use rand::Rng; /// let val: f64 = thread_rng().sample(StandardNormal); /// println!("{}", val); /// ``` +/// +/// # Notes +/// +/// Implemented via the ZIGNOR variant[^1] of the Ziggurat method. +/// +/// [^1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to +/// Generate Normal Random Samples*]( +/// https://www.doornik.com/research/ziggurat.pdf). +/// Nuffield College, Oxford #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct StandardNormal; @@ -92,13 +100,28 @@ impl Distribution for StandardNormal { } } -/// The normal distribution `N(mean, std_dev**2)`. +/// The [Normal distribution](https://en.wikipedia.org/wiki/Normal_distribution) `N(μ, σ²)`. /// -/// This uses the ZIGNOR variant of the Ziggurat method, see [`StandardNormal`] -/// for more details. +/// The Normal distribution, also known as the Gaussian distribution or +/// bell curve, is a continuous probability distribution with mean +/// `μ` (`mu`) and standard deviation `σ` (`sigma`). +/// It is used to model continuous data that tend to cluster around a mean. +/// The Normal distribution is symmetric and characterized by its bell-shaped curve. /// -/// Note that [`StandardNormal`] is an optimised implementation for mean 0, and -/// standard deviation 1. +/// See [`StandardNormal`](crate::StandardNormal) for an +/// optimised implementation for `μ = 0` and `σ = 1`. +/// +/// # Density function +/// +/// `f(x) = (1 / sqrt(2π σ²)) * exp(-((x - μ)² / (2σ²)))` +/// +/// # Plot +/// +/// The following diagram shows the Normal distribution with various values of `μ` +/// and `σ`. +/// The blue curve is the [`StandardNormal`](crate::StandardNormal) distribution, `N(0, 1)`. +/// +/// ![Normal distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/normal.svg) /// /// # Example /// @@ -111,7 +134,14 @@ impl Distribution for StandardNormal { /// println!("{} is from a N(2, 9) distribution", v) /// ``` /// -/// [`StandardNormal`]: crate::StandardNormal +/// # Notes +/// +/// Implemented via the ZIGNOR variant[^1] of the Ziggurat method. +/// +/// [^1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to +/// Generate Normal Random Samples*]( +/// https://www.doornik.com/research/ziggurat.pdf). +/// Nuffield College, Oxford #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Normal @@ -216,10 +246,18 @@ where } } -/// The log-normal distribution `ln N(mean, std_dev**2)`. +/// The [log-normal distribution](https://en.wikipedia.org/wiki/Log-normal_distribution) `ln N(μ, σ²)`. +/// +/// This is the distribution of the random variable `X = exp(Y)` where `Y` is +/// normally distributed with mean `μ` and variance `σ²`. In other words, if +/// `X` is log-normal distributed, then `ln(X)` is `N(μ, σ²)` distributed. +/// +/// # Plot +/// +/// The following diagram shows the log-normal distribution with various values +/// of `μ` and `σ`. /// -/// If `X` is log-normal distributed, then `ln(X)` is `N(mean, std_dev**2)` -/// distributed. +/// ![Log-normal distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/log_normal.svg) /// /// # Example /// diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index 18540a2bfcf..f8f62170f59 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -28,7 +28,26 @@ impl fmt::Display for Error { #[cfg(feature = "std")] impl std::error::Error for Error {} -/// The [normal-inverse Gaussian distribution](https://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) +/// The [normal-inverse Gaussian distribution](https://en.wikipedia.org/wiki/Normal-inverse_Gaussian_distribution) `NIG(α, β)`. +/// +/// This is a continuous probability distribution with two parameters, +/// `α` (`alpha`) and `β` (`beta`), defined in `(-∞, ∞)`. +/// It is also known as the normal-Wald distribution. +/// +/// # Plot +/// +/// The following plot shows the normal-inverse Gaussian distribution with various values of `α` and `β`. +/// +/// ![Normal-inverse Gaussian distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/normal_inverse_gaussian.svg) +/// +/// # Example +/// ``` +/// use rand_distr::{NormalInverseGaussian, Distribution}; +/// +/// let norm_inv_gauss = NormalInverseGaussian::new(2.0, 1.0).unwrap(); +/// let v = norm_inv_gauss.sample(&mut rand::thread_rng()); +/// println!("{} is from a normal-inverse Gaussian(2, 1) distribution", v); +/// ``` #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct NormalInverseGaussian diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index 8c785e5f292..ba0465f7e65 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -6,14 +6,26 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Pareto distribution. +//! The Pareto distribution `Pareto(xₘ, α)`. use crate::{Distribution, OpenClosed01}; use core::fmt; use num_traits::Float; use rand::Rng; -/// Samples floating-point numbers according to the Pareto distribution +/// The [Pareto distribution](https://en.wikipedia.org/wiki/Pareto_distribution) `Pareto(xₘ, α)`. +/// +/// The Pareto distribution is a continuous probability distribution with +/// scale parameter `xₘ` ( or `k`) and shape parameter `α`. +/// +/// # Plot +/// +/// The following plot shows the Pareto distribution with various values of +/// `xₘ` and `α`. +/// Note how the shape parameter `α` corresponds to the height of the jump +/// in density at `x = xₘ`, and to the rate of decay in the tail. +/// +/// ![Pareto distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/pareto.svg) /// /// # Example /// ``` diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index bc028e6a4aa..df5361d7049 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -12,13 +12,20 @@ use core::fmt; use num_traits::Float; use rand::Rng; -/// The PERT distribution. +/// The [PERT distribution](https://en.wikipedia.org/wiki/PERT_distribution) `PERT(min, max, mode, shape)`. /// /// Similar to the [`Triangular`] distribution, the PERT distribution is /// parameterised by a range and a mode within that range. Unlike the /// [`Triangular`] distribution, the probability density function of the PERT /// distribution is smooth, with a configurable weighting around the mode. /// +/// # Plot +/// +/// The following plot shows the PERT distribution with `min = -1`, `max = 1`, +/// and various values of `mode` and `shape`. +/// +/// ![PERT distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/pert.svg) +/// /// # Example /// /// ```rust diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index d50769da191..c84d4dce35e 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -7,17 +7,28 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Poisson distribution. +//! The Poisson distribution `Poisson(λ)`. use crate::{Cauchy, Distribution, Standard}; use core::fmt; use num_traits::{Float, FloatConst}; use rand::Rng; -/// The Poisson distribution `Poisson(lambda)`. +/// The [Poisson distribution](https://en.wikipedia.org/wiki/Poisson_distribution) `Poisson(λ)`. /// -/// This distribution has a density function: -/// `f(k) = lambda^k * exp(-lambda) / k!` for `k >= 0`. +/// The Poisson distribution is a discrete probability distribution with +/// rate parameter `λ` (`lambda`). It models the number of events occurring in a fixed +/// interval of time or space. +/// +/// This distribution has density function: +/// `f(k) = λ^k * exp(-λ) / k!` for `k >= 0`. +/// +/// # Plot +/// +/// The following plot shows the Poisson distribution with various values of `λ`. +/// Note how the expected number of events increases with `λ`. +/// +/// ![Poisson distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/poisson.svg) /// /// # Example /// diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 908484f3737..6ef521be25f 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -6,22 +6,38 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Skew Normal distribution. +//! The Skew Normal distribution `SN(ξ, ω, α)`. use crate::{Distribution, StandardNormal}; use core::fmt; use num_traits::Float; use rand::Rng; -/// The [skew normal distribution] `SN(location, scale, shape)`. +/// The [skew normal distribution](https://en.wikipedia.org/wiki/Skew_normal_distribution) `SN(ξ, ω, α)`. /// /// The skew normal distribution is a generalization of the -/// [`Normal`] distribution to allow for non-zero skewness. +/// [`Normal`](crate::Normal) distribution to allow for non-zero skewness. +/// It has location parameter `ξ` (`xi`), scale parameter `ω` (`omega`), +/// and shape parameter `α` (`alpha`). +/// +/// The `ξ` and `ω` parameters correspond to the mean `μ` and standard +/// deviation `σ` of the normal distribution, respectively. +/// The `α` parameter controls the skewness. +/// +/// # Density function /// /// It has the density function, for `scale > 0`, /// `f(x) = 2 / scale * phi((x - location) / scale) * Phi(alpha * (x - location) / scale)` /// where `phi` and `Phi` are the density and distribution of a standard normal variable. /// +/// # Plot +/// +/// The following plot shows the skew normal distribution with `location = 0`, `scale = 1` +/// (corresponding to the [`standard normal distribution`](crate::StandardNormal)), and +/// various values of `shape`. +/// +/// ![Skew normal distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/skew_normal.svg) +/// /// # Example /// /// ``` diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index 795a5473e76..ab7722ac030 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -12,7 +12,7 @@ use core::fmt; use num_traits::Float; use rand::Rng; -/// The triangular distribution. +/// The [triangular distribution](https://en.wikipedia.org/wiki/Triangular_distribution) `Triangular(min, max, mode)`. /// /// A continuous probability distribution parameterised by a range, and a mode /// (most likely value) within that range. @@ -20,6 +20,13 @@ use rand::Rng; /// The probability density function is triangular. For a similar distribution /// with a smooth PDF, see the [`Pert`] distribution. /// +/// # Plot +/// +/// The following plot shows the triangular distribution with various values of +/// `min`, `max`, and `mode`. +/// +/// ![Triangular distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/triangular.svg) +/// /// # Example /// /// ```rust diff --git a/rand_distr/src/unit_ball.rs b/rand_distr/src/unit_ball.rs index 84ba6909abf..1cc7119b7fb 100644 --- a/rand_distr/src/unit_ball.rs +++ b/rand_distr/src/unit_ball.rs @@ -10,11 +10,22 @@ use crate::{uniform::SampleUniform, Distribution, Uniform}; use num_traits::Float; use rand::Rng; -/// Samples uniformly from the unit ball (surface and interior) in three -/// dimensions. +/// Samples uniformly from the volume of the unit ball in three dimensions. /// /// Implemented via rejection sampling. /// +/// For a distribution that samples only from the surface of the unit ball, +/// see [`UnitSphere`](crate::UnitSphere). +/// +/// For a similar distribution in two dimensions, see [`UnitDisc`](crate::UnitDisc). +/// +/// # Plot +/// +/// The following plot shows the unit ball in three dimensions. +/// This distribution samples individual points from the entire volume +/// of the ball. +/// +/// ![Unit ball](https://raw.githubusercontent.com/rust-random/charts/main/charts/unit_ball.svg) /// /// # Example /// diff --git a/rand_distr/src/unit_circle.rs b/rand_distr/src/unit_circle.rs index 8b67545300a..a23cec25225 100644 --- a/rand_distr/src/unit_circle.rs +++ b/rand_distr/src/unit_circle.rs @@ -10,10 +10,20 @@ use crate::{uniform::SampleUniform, Distribution, Uniform}; use num_traits::Float; use rand::Rng; -/// Samples uniformly from the edge of the unit circle in two dimensions. +/// Samples uniformly from the circumference of the unit circle in two dimensions. /// /// Implemented via a method by von Neumann[^1]. /// +/// For a distribution that also samples from the interior of the unit circle, +/// see [`UnitDisc`](crate::UnitDisc). +/// +/// For a similar distribution in three dimensions, see [`UnitSphere`](crate::UnitSphere). +/// +/// # Plot +/// +/// The following plot shows the unit circle. +/// +/// ![Unit circle](https://raw.githubusercontent.com/rust-random/charts/main/charts/unit_circle.svg) /// /// # Example /// diff --git a/rand_distr/src/unit_disc.rs b/rand_distr/src/unit_disc.rs index bcf33c7924f..4ba5256265f 100644 --- a/rand_distr/src/unit_disc.rs +++ b/rand_distr/src/unit_disc.rs @@ -14,6 +14,17 @@ use rand::Rng; /// /// Implemented via rejection sampling. /// +/// For a distribution that samples only from the circumference of the unit disc, +/// see [`UnitCircle`](crate::UnitCircle). +/// +/// For a similar distribution in three dimensions, see [`UnitBall`](crate::UnitBall). +/// +/// # Plot +/// +/// The following plot shows the unit disc. +/// This distribution samples individual points from the entire area of the disc. +/// +/// ![Unit disc](https://raw.githubusercontent.com/rust-random/charts/main/charts/unit_disc.svg) /// /// # Example /// diff --git a/rand_distr/src/unit_sphere.rs b/rand_distr/src/unit_sphere.rs index 5e7f8fe7712..61f48e0c067 100644 --- a/rand_distr/src/unit_sphere.rs +++ b/rand_distr/src/unit_sphere.rs @@ -14,6 +14,18 @@ use rand::Rng; /// /// Implemented via a method by Marsaglia[^1]. /// +/// For a distribution that also samples from the interior of the sphere, +/// see [`UnitBall`](crate::UnitBall). +/// +/// For a similar distribution in two dimensions, see [`UnitCircle`](crate::UnitCircle). +/// +/// # Plot +/// +/// The following plot shows the unit sphere as a wireframe. +/// The wireframe is meant to illustrate that this distribution samples +/// from the surface of the sphere only, not from the interior. +/// +/// ![Unit sphere](https://raw.githubusercontent.com/rust-random/charts/main/charts/unit_sphere.svg) /// /// # Example /// diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index 4d094b1f0d1..e6f80736a4b 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -6,14 +6,24 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Weibull distribution. +//! The Weibull distribution `Weibull(λ, k)` use crate::{Distribution, OpenClosed01}; use core::fmt; use num_traits::Float; use rand::Rng; -/// Samples floating-point numbers according to the Weibull distribution +/// The [Weibull distribution](https://en.wikipedia.org/wiki/Weibull_distribution) `Weibull(λ, k)`. +/// +/// This is a family of continuous probability distributions with +/// scale parameter `λ` (`lambda`) and shape parameter `k`. It is used +/// to model reliability data, life data, and accelerated life testing data. +/// +/// # Plot +/// +/// The following plot shows the Weibull distribution with various values of `λ` and `k`. +/// +/// ![Weibull distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/weibull.svg) /// /// # Example /// ``` diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 88848600e82..66164128102 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -13,14 +13,23 @@ use core::fmt; use num_traits::Float; use rand::{distributions::OpenClosed01, Rng}; -/// Samples integers according to the [zeta distribution]. +/// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) `Zeta(a)`. /// -/// The zeta distribution is a limit of the [`Zipf`] distribution. Sometimes it -/// is called one of the following: discrete Pareto, Riemann-Zeta, Zipf, or -/// Zipf–Estoup distribution. +/// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) +/// is a discrete probability distribution with parameter `a`. +/// It is a special case of the [`Zipf`] distribution with `n = ∞`. +/// It is also known as the discrete Pareto, Riemann-Zeta, Zipf, or Zipf–Estoup distribution. /// -/// It has the density function `f(k) = k^(-a) / C(a)` for `k >= 1`, where `a` -/// is the parameter and `C(a)` is the Riemann zeta function. +/// # Density function +/// +/// `f(k) = k^(-a) / ζ(a)` for `k >= 1`, where `ζ` is the +/// [Riemann zeta function](https://en.wikipedia.org/wiki/Riemann_zeta_function). +/// +/// # Plot +/// +/// The following plot illustrates the zeta distribution for various values of `a`. +/// +/// ![Zeta distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/zeta.svg) /// /// # Example /// ``` @@ -31,7 +40,7 @@ use rand::{distributions::OpenClosed01, Rng}; /// println!("{}", val); /// ``` /// -/// # Remarks +/// # Notes /// /// The zeta distribution has no upper limit. Sampled values may be infinite. /// In particular, a value of infinity might be returned for the following @@ -41,11 +50,9 @@ use rand::{distributions::OpenClosed01, Rng}; /// /// # Implementation details /// -/// We are using the algorithm from [Non-Uniform Random Variate Generation], +/// We are using the algorithm from +/// [Non-Uniform Random Variate Generation](https://doi.org/10.1007/978-1-4613-8643-8), /// Section 6.1, page 551. -/// -/// [zeta distribution]: https://en.wikipedia.org/wiki/Zeta_distribution -/// [Non-Uniform Random Variate Generation]: https://doi.org/10.1007/978-1-4613-8643-8 #[derive(Clone, Copy, Debug, PartialEq)] pub struct Zeta where @@ -125,15 +132,22 @@ where } } -/// Samples integers according to the Zipf distribution. +/// The Zipf (Zipfian) distribution `Zipf(n, s)`. +/// +/// The samples follow [Zipf's law](https://en.wikipedia.org/wiki/Zipf%27s_law): +/// The frequency of each sample from a finite set of size `n` is inversely +/// proportional to a power of its frequency rank (with exponent `s`). +/// +/// For large `n`, this converges to the [`Zeta`](crate::Zeta) distribution. +/// +/// For `s = 0`, this becomes a [`uniform`](crate::Uniform) distribution. /// -/// The samples follow Zipf's law: The frequency of each sample from a finite -/// set of size `n` is inversely proportional to a power of its frequency rank -/// (with exponent `s`). +/// # Plot /// -/// For large `n`, this converges to the [`Zeta`] distribution. +/// The following plot illustrates the Zipf distribution for `n = 10` and +/// various values of `s`. /// -/// For `s = 0`, this becomes a uniform distribution. +/// ![Zipf distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/zipf.svg) /// /// # Example /// ``` diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index a8a46b0e3cc..80453496735 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -6,7 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Bernoulli distribution. +//! The Bernoulli distribution `Bernoulli(p)`. use crate::distributions::Distribution; use crate::Rng; @@ -15,9 +15,18 @@ use core::fmt; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -/// The Bernoulli distribution. +/// The [Bernoulli distribution](https://en.wikipedia.org/wiki/Bernoulli_distribution) `Bernoulli(p)`. /// -/// This is a special case of the Binomial distribution where `n = 1`. +/// This distribution describes a single boolean random variable, which is true +/// with probability `p` and false with probability `1 - p`. +/// It is a special case of the Binomial distribution with `n = 1`. +/// +/// # Plot +/// +/// The following plot shows the Bernoulli distribution with `p = 0.1`, +/// `p = 0.5`, and `p = 0.9`. +/// +/// ![Bernoulli distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/bernoulli.svg) /// /// # Example /// diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 48f40bebc30..8a887ce3e85 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -20,7 +20,7 @@ use core::fmt::Debug; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; -/// A distribution using weighted sampling of discrete items +/// A distribution using weighted sampling of discrete items. /// /// Sampling a `WeightedIndex` distribution returns the index of a randomly /// selected element from the iterator used when the `WeightedIndex` was From 763dbc5bbb30c4c9c89bc20fcd09ae7caee783f6 Mon Sep 17 00:00:00 2001 From: Michael Dyer <59163924+MichaelOwenDyer@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:05:50 +0200 Subject: [PATCH 392/443] `rand_distr`: Rename Zeta parameter from `a` to `s` (#1466) --- rand_distr/src/zipf.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 66164128102..c8e3fef9b39 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -13,21 +13,21 @@ use core::fmt; use num_traits::Float; use rand::{distributions::OpenClosed01, Rng}; -/// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) `Zeta(a)`. +/// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) `Zeta(s)`. /// /// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) -/// is a discrete probability distribution with parameter `a`. +/// is a discrete probability distribution with parameter `s`. /// It is a special case of the [`Zipf`] distribution with `n = ∞`. /// It is also known as the discrete Pareto, Riemann-Zeta, Zipf, or Zipf–Estoup distribution. /// /// # Density function /// -/// `f(k) = k^(-a) / ζ(a)` for `k >= 1`, where `ζ` is the +/// `f(k) = k^(-s) / ζ(s)` for `k >= 1`, where `ζ` is the /// [Riemann zeta function](https://en.wikipedia.org/wiki/Riemann_zeta_function). /// /// # Plot /// -/// The following plot illustrates the zeta distribution for various values of `a`. +/// The following plot illustrates the zeta distribution for various values of `s`. /// /// ![Zeta distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/zeta.svg) /// @@ -46,7 +46,7 @@ use rand::{distributions::OpenClosed01, Rng}; /// In particular, a value of infinity might be returned for the following /// reasons: /// 1. it is the best representation in the type `F` of the actual sample. -/// 2. to prevent infinite loops for very small `a`. +/// 2. to prevent infinite loops for very small `s`. /// /// # Implementation details /// @@ -60,21 +60,21 @@ where Standard: Distribution, OpenClosed01: Distribution, { - a_minus_1: F, + s_minus_1: F, b: F, } /// Error type returned from `Zeta::new`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ZetaError { - /// `a <= 1` or `nan`. - ATooSmall, + /// `s <= 1` or `nan`. + STooSmall, } impl fmt::Display for ZetaError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - ZetaError::ATooSmall => "a <= 1 or is NaN in Zeta distribution", + ZetaError::STooSmall => "s <= 1 or is NaN in Zeta distribution", }) } } @@ -88,17 +88,17 @@ where Standard: Distribution, OpenClosed01: Distribution, { - /// Construct a new `Zeta` distribution with given `a` parameter. + /// Construct a new `Zeta` distribution with given `s` parameter. #[inline] - pub fn new(a: F) -> Result, ZetaError> { - if !(a > F::one()) { - return Err(ZetaError::ATooSmall); + pub fn new(s: F) -> Result, ZetaError> { + if !(s > F::one()) { + return Err(ZetaError::STooSmall); } - let a_minus_1 = a - F::one(); + let s_minus_1 = s - F::one(); let two = F::one() + F::one(); Ok(Zeta { - a_minus_1, - b: two.powf(a_minus_1), + s_minus_1, + b: two.powf(s_minus_1), }) } } @@ -113,16 +113,16 @@ where fn sample(&self, rng: &mut R) -> F { loop { let u = rng.sample(OpenClosed01); - let x = u.powf(-F::one() / self.a_minus_1).floor(); + let x = u.powf(-F::one() / self.s_minus_1).floor(); debug_assert!(x >= F::one()); if x.is_infinite() { - // For sufficiently small `a`, `x` will always be infinite, + // For sufficiently small `s`, `x` will always be infinite, // which is rejected, resulting in an infinite loop. We avoid // this by always returning infinity instead. return x; } - let t = (F::one() + F::one() / x).powf(self.a_minus_1); + let t = (F::one() + F::one() / x).powf(self.s_minus_1); let v = rng.sample(Standard); if v * x * (t - F::one()) * self.b <= t * (self.b - F::one()) { From d17ce4e0a10d3dfba7e30fa06588544ef7625534 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 11 Jul 2024 14:50:22 +0100 Subject: [PATCH 393/443] rand_distr: split gamma module (#1464) Move Beta, Student's t, Fisher-F, Chi-squared and Zeta distributions to their own modules. --- rand_distr/src/beta.rs | 298 ++++++++++ rand_distr/src/binomial.rs | 2 +- rand_distr/src/cauchy.rs | 2 +- rand_distr/src/chi_squared.rs | 179 ++++++ rand_distr/src/dirichlet.rs | 6 +- rand_distr/src/exponential.rs | 2 +- rand_distr/src/fisher_f.rs | 131 +++++ rand_distr/src/frechet.rs | 2 +- rand_distr/src/gamma.rs | 632 +--------------------- rand_distr/src/geometric.rs | 2 +- rand_distr/src/gumbel.rs | 2 +- rand_distr/src/hypergeometric.rs | 2 +- rand_distr/src/inverse_gaussian.rs | 2 +- rand_distr/src/lib.rs | 17 +- rand_distr/src/normal.rs | 2 +- rand_distr/src/normal_inverse_gaussian.rs | 2 +- rand_distr/src/pareto.rs | 2 +- rand_distr/src/poisson.rs | 2 +- rand_distr/src/skew_normal.rs | 2 +- rand_distr/src/student_t.rs | 107 ++++ rand_distr/src/weibull.rs | 2 +- rand_distr/src/zeta.rs | 192 +++++++ rand_distr/src/zipf.rs | 186 +------ src/distributions/bernoulli.rs | 2 +- src/distributions/weighted_index.rs | 2 +- 25 files changed, 952 insertions(+), 828 deletions(-) create mode 100644 rand_distr/src/beta.rs create mode 100644 rand_distr/src/chi_squared.rs create mode 100644 rand_distr/src/fisher_f.rs create mode 100644 rand_distr/src/student_t.rs create mode 100644 rand_distr/src/zeta.rs diff --git a/rand_distr/src/beta.rs b/rand_distr/src/beta.rs new file mode 100644 index 00000000000..9ef5d0024df --- /dev/null +++ b/rand_distr/src/beta.rs @@ -0,0 +1,298 @@ +// Copyright 2018 Developers of the Rand project. +// Copyright 2013 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Beta distribution. + +use crate::{Distribution, Open01}; +use core::fmt; +use num_traits::Float; +use rand::Rng; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// The algorithm used for sampling the Beta distribution. +/// +/// Reference: +/// +/// R. C. H. Cheng (1978). +/// Generating beta variates with nonintegral shape parameters. +/// Communications of the ACM 21, 317-322. +/// https://doi.org/10.1145/359460.359482 +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +enum BetaAlgorithm { + BB(BB), + BC(BC), +} + +/// Algorithm BB for `min(alpha, beta) > 1`. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +struct BB { + alpha: N, + beta: N, + gamma: N, +} + +/// Algorithm BC for `min(alpha, beta) <= 1`. +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +struct BC { + alpha: N, + beta: N, + kappa1: N, + kappa2: N, +} + +/// The [Beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) `Beta(α, β)`. +/// +/// The Beta distribution is a continuous probability distribution +/// defined on the interval `[0, 1]`. It is the conjugate prior for the +/// parameter `p` of the [`Binomial`][crate::Binomial] distribution. +/// +/// It has two shape parameters `α` (alpha) and `β` (beta) which control +/// the shape of the distribution. Both `a` and `β` must be greater than zero. +/// The distribution is symmetric when `α = β`. +/// +/// # Plot +/// +/// The plot shows the Beta distribution with various combinations +/// of `α` and `β`. +/// +/// ![Beta distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/beta.svg) +/// +/// # Example +/// +/// ``` +/// use rand_distr::{Distribution, Beta}; +/// +/// let beta = Beta::new(2.0, 5.0).unwrap(); +/// let v = beta.sample(&mut rand::thread_rng()); +/// println!("{} is from a Beta(2, 5) distribution", v); +/// ``` +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct Beta +where + F: Float, + Open01: Distribution, +{ + a: F, + b: F, + switched_params: bool, + algorithm: BetaAlgorithm, +} + +/// Error type returned from [`Beta::new`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum Error { + /// `alpha <= 0` or `nan`. + AlphaTooSmall, + /// `beta <= 0` or `nan`. + BetaTooSmall, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::AlphaTooSmall => "alpha is not positive in beta distribution", + Error::BetaTooSmall => "beta is not positive in beta distribution", + }) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl Beta +where + F: Float, + Open01: Distribution, +{ + /// Construct an object representing the `Beta(alpha, beta)` + /// distribution. + pub fn new(alpha: F, beta: F) -> Result, Error> { + if !(alpha > F::zero()) { + return Err(Error::AlphaTooSmall); + } + if !(beta > F::zero()) { + return Err(Error::BetaTooSmall); + } + // From now on, we use the notation from the reference, + // i.e. `alpha` and `beta` are renamed to `a0` and `b0`. + let (a0, b0) = (alpha, beta); + let (a, b, switched_params) = if a0 < b0 { + (a0, b0, false) + } else { + (b0, a0, true) + }; + if a > F::one() { + // Algorithm BB + let alpha = a + b; + + let two = F::from(2.).unwrap(); + let beta_numer = alpha - two; + let beta_denom = two * a * b - alpha; + let beta = (beta_numer / beta_denom).sqrt(); + + let gamma = a + F::one() / beta; + + Ok(Beta { + a, + b, + switched_params, + algorithm: BetaAlgorithm::BB(BB { alpha, beta, gamma }), + }) + } else { + // Algorithm BC + // + // Here `a` is the maximum instead of the minimum. + let (a, b, switched_params) = (b, a, !switched_params); + let alpha = a + b; + let beta = F::one() / b; + let delta = F::one() + a - b; + let kappa1 = delta + * (F::from(1. / 18. / 4.).unwrap() + F::from(3. / 18. / 4.).unwrap() * b) + / (a * beta - F::from(14. / 18.).unwrap()); + let kappa2 = F::from(0.25).unwrap() + + (F::from(0.5).unwrap() + F::from(0.25).unwrap() / delta) * b; + + Ok(Beta { + a, + b, + switched_params, + algorithm: BetaAlgorithm::BC(BC { + alpha, + beta, + kappa1, + kappa2, + }), + }) + } + } +} + +impl Distribution for Beta +where + F: Float, + Open01: Distribution, +{ + fn sample(&self, rng: &mut R) -> F { + let mut w; + match self.algorithm { + BetaAlgorithm::BB(algo) => { + loop { + // 1. + let u1 = rng.sample(Open01); + let u2 = rng.sample(Open01); + let v = algo.beta * (u1 / (F::one() - u1)).ln(); + w = self.a * v.exp(); + let z = u1 * u1 * u2; + let r = algo.gamma * v - F::from(4.).unwrap().ln(); + let s = self.a + r - w; + // 2. + if s + F::one() + F::from(5.).unwrap().ln() >= F::from(5.).unwrap() * z { + break; + } + // 3. + let t = z.ln(); + if s >= t { + break; + } + // 4. + if !(r + algo.alpha * (algo.alpha / (self.b + w)).ln() < t) { + break; + } + } + } + BetaAlgorithm::BC(algo) => { + loop { + let z; + // 1. + let u1 = rng.sample(Open01); + let u2 = rng.sample(Open01); + if u1 < F::from(0.5).unwrap() { + // 2. + let y = u1 * u2; + z = u1 * y; + if F::from(0.25).unwrap() * u2 + z - y >= algo.kappa1 { + continue; + } + } else { + // 3. + z = u1 * u1 * u2; + if z <= F::from(0.25).unwrap() { + let v = algo.beta * (u1 / (F::one() - u1)).ln(); + w = self.a * v.exp(); + break; + } + // 4. + if z >= algo.kappa2 { + continue; + } + } + // 5. + let v = algo.beta * (u1 / (F::one() - u1)).ln(); + w = self.a * v.exp(); + if !(algo.alpha * ((algo.alpha / (self.b + w)).ln() + v) + - F::from(4.).unwrap().ln() + < z.ln()) + { + break; + }; + } + } + }; + // 5. for BB, 6. for BC + if !self.switched_params { + if w == F::infinity() { + // Assuming `b` is finite, for large `w`: + return F::one(); + } + w / (self.b + w) + } else { + self.b / (self.b + w) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_beta() { + let beta = Beta::new(1.0, 2.0).unwrap(); + let mut rng = crate::test::rng(201); + for _ in 0..1000 { + beta.sample(&mut rng); + } + } + + #[test] + #[should_panic] + fn test_beta_invalid_dof() { + Beta::new(0., 0.).unwrap(); + } + + #[test] + fn test_beta_small_param() { + let beta = Beta::::new(1e-3, 1e-3).unwrap(); + let mut rng = crate::test::rng(206); + for i in 0..1000 { + assert!(!beta.sample(&mut rng).is_nan(), "failed at i={}", i); + } + } + + #[test] + fn beta_distributions_can_be_compared() { + assert_eq!(Beta::new(1.0, 2.0), Beta::new(1.0, 2.0)); + } +} diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 514dbeca904..02f7fc37ff0 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -52,7 +52,7 @@ pub struct Binomial { p: f64, } -/// Error type returned from `Binomial::new`. +/// Error type returned from [`Binomial::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `p < 0` or `nan`. diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 6d4ff4ec18d..1c6f91236cc 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -64,7 +64,7 @@ where scale: F, } -/// Error type returned from `Cauchy::new`. +/// Error type returned from [`Cauchy::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `scale <= 0` or `nan`. diff --git a/rand_distr/src/chi_squared.rs b/rand_distr/src/chi_squared.rs new file mode 100644 index 00000000000..fcdc397bf6e --- /dev/null +++ b/rand_distr/src/chi_squared.rs @@ -0,0 +1,179 @@ +// Copyright 2018 Developers of the Rand project. +// Copyright 2013 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Chi-squared distribution. + +use self::ChiSquaredRepr::*; + +use crate::{Distribution, Exp1, Gamma, Open01, StandardNormal}; +use core::fmt; +use num_traits::Float; +use rand::Rng; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// The [chi-squared distribution](https://en.wikipedia.org/wiki/Chi-squared_distribution) `χ²(k)`. +/// +/// The chi-squared distribution is a continuous probability +/// distribution with parameter `k > 0` degrees of freedom. +/// +/// For `k > 0` integral, this distribution is the sum of the squares +/// of `k` independent standard normal random variables. For other +/// `k`, this uses the equivalent characterisation +/// `χ²(k) = Gamma(k/2, 2)`. +/// +/// # Plot +/// +/// The plot shows the chi-squared distribution with various degrees +/// of freedom. +/// +/// ![Chi-squared distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/chi_squared.svg) +/// +/// # Example +/// +/// ``` +/// use rand_distr::{ChiSquared, Distribution}; +/// +/// let chi = ChiSquared::new(11.0).unwrap(); +/// let v = chi.sample(&mut rand::thread_rng()); +/// println!("{} is from a χ²(11) distribution", v) +/// ``` +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct ChiSquared +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + repr: ChiSquaredRepr, +} + +/// Error type returned from [`ChiSquared::new`] and [`StudentT::new`](crate::StudentT::new). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum Error { + /// `0.5 * k <= 0` or `nan`. + DoFTooSmall, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::DoFTooSmall => { + "degrees-of-freedom k is not positive in chi-squared distribution" + } + }) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +enum ChiSquaredRepr +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + // k == 1, Gamma(alpha, ..) is particularly slow for alpha < 1, + // e.g. when alpha = 1/2 as it would be for this case, so special- + // casing and using the definition of N(0,1)^2 is faster. + DoFExactlyOne, + DoFAnythingElse(Gamma), +} + +impl ChiSquared +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + /// Create a new chi-squared distribution with degrees-of-freedom + /// `k`. + pub fn new(k: F) -> Result, Error> { + let repr = if k == F::one() { + DoFExactlyOne + } else { + if !(F::from(0.5).unwrap() * k > F::zero()) { + return Err(Error::DoFTooSmall); + } + DoFAnythingElse(Gamma::new(F::from(0.5).unwrap() * k, F::from(2.0).unwrap()).unwrap()) + }; + Ok(ChiSquared { repr }) + } +} +impl Distribution for ChiSquared +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + fn sample(&self, rng: &mut R) -> F { + match self.repr { + DoFExactlyOne => { + // k == 1 => N(0,1)^2 + let norm: F = rng.sample(StandardNormal); + norm * norm + } + DoFAnythingElse(ref g) => g.sample(rng), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_chi_squared_one() { + let chi = ChiSquared::new(1.0).unwrap(); + let mut rng = crate::test::rng(201); + for _ in 0..1000 { + chi.sample(&mut rng); + } + } + #[test] + fn test_chi_squared_small() { + let chi = ChiSquared::new(0.5).unwrap(); + let mut rng = crate::test::rng(202); + for _ in 0..1000 { + chi.sample(&mut rng); + } + } + #[test] + fn test_chi_squared_large() { + let chi = ChiSquared::new(30.0).unwrap(); + let mut rng = crate::test::rng(203); + for _ in 0..1000 { + chi.sample(&mut rng); + } + } + #[test] + #[should_panic] + fn test_chi_squared_invalid_dof() { + ChiSquared::new(-1.0).unwrap(); + } + + #[test] + fn gamma_distributions_can_be_compared() { + assert_eq!(Gamma::new(1.0, 2.0), Gamma::new(1.0, 2.0)); + } + + #[test] + fn chi_squared_distributions_can_be_compared() { + assert_eq!(ChiSquared::new(1.0), ChiSquared::new(1.0)); + } +} diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 9605308432c..aae1e0750ff 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -31,7 +31,7 @@ where samplers: [Gamma; N], } -/// Error type returned from `DirchletFromGamma::new`. +/// Error type returned from [`DirchletFromGamma::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum DirichletFromGammaError { /// Gamma::new(a, 1) failed. @@ -103,7 +103,7 @@ where samplers: Box<[Beta]>, } -/// Error type returned from `DirchletFromBeta::new`. +/// Error type returned from [`DirchletFromBeta::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum DirichletFromBetaError { /// Beta::new(a, b) failed. @@ -226,7 +226,7 @@ where repr: DirichletRepr, } -/// Error type returned from `Dirchlet::new`. +/// Error type returned from [`Dirichlet::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `alpha.len() < 2`. diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index 4c919b20960..c31fc9520ec 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -130,7 +130,7 @@ where lambda_inverse: F, } -/// Error type returned from `Exp::new`. +/// Error type returned from [`Exp::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `lambda < 0` or `nan`. diff --git a/rand_distr/src/fisher_f.rs b/rand_distr/src/fisher_f.rs new file mode 100644 index 00000000000..1a16b6d6363 --- /dev/null +++ b/rand_distr/src/fisher_f.rs @@ -0,0 +1,131 @@ +// Copyright 2018 Developers of the Rand project. +// Copyright 2013 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Fisher F-distribution. + +use crate::{ChiSquared, Distribution, Exp1, Open01, StandardNormal}; +use core::fmt; +use num_traits::Float; +use rand::Rng; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// The [Fisher F-distribution](https://en.wikipedia.org/wiki/F-distribution) `F(m, n)`. +/// +/// This distribution is equivalent to the ratio of two normalised +/// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) / +/// (χ²(n)/n)`. +/// +/// # Plot +/// +/// The plot shows the F-distribution with various values of `m` and `n`. +/// +/// ![F-distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/fisher_f.svg) +/// +/// # Example +/// +/// ``` +/// use rand_distr::{FisherF, Distribution}; +/// +/// let f = FisherF::new(2.0, 32.0).unwrap(); +/// let v = f.sample(&mut rand::thread_rng()); +/// println!("{} is from an F(2, 32) distribution", v) +/// ``` +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct FisherF +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + numer: ChiSquared, + denom: ChiSquared, + // denom_dof / numer_dof so that this can just be a straight + // multiplication, rather than a division. + dof_ratio: F, +} + +/// Error type returned from [`FisherF::new`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub enum Error { + /// `m <= 0` or `nan`. + MTooSmall, + /// `n <= 0` or `nan`. + NTooSmall, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::MTooSmall => "m is not positive in Fisher F distribution", + Error::NTooSmall => "n is not positive in Fisher F distribution", + }) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl FisherF +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + /// Create a new `FisherF` distribution, with the given parameter. + pub fn new(m: F, n: F) -> Result, Error> { + let zero = F::zero(); + if !(m > zero) { + return Err(Error::MTooSmall); + } + if !(n > zero) { + return Err(Error::NTooSmall); + } + + Ok(FisherF { + numer: ChiSquared::new(m).unwrap(), + denom: ChiSquared::new(n).unwrap(), + dof_ratio: n / m, + }) + } +} +impl Distribution for FisherF +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + fn sample(&self, rng: &mut R) -> F { + self.numer.sample(rng) / self.denom.sample(rng) * self.dof_ratio + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_f() { + let f = FisherF::new(2.0, 32.0).unwrap(); + let mut rng = crate::test::rng(204); + for _ in 0..1000 { + f.sample(&mut rng); + } + } + + #[test] + fn fisher_f_distributions_can_be_compared() { + assert_eq!(FisherF::new(1.0, 2.0), FisherF::new(1.0, 2.0)); + } +} diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index b274946d66e..831561d607e 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -55,7 +55,7 @@ where shape: F, } -/// Error type returned from `Frechet::new`. +/// Error type returned from [`Frechet::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// location is infinite or NaN diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 23051e45d33..4699bbb6e81 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -7,17 +7,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Gamma and derived distributions. +//! The Gamma distribution. -// We use the variable names from the published reference, therefore this -// warning is not helpful. -#![allow(clippy::many_single_char_names)] - -use self::ChiSquaredRepr::*; use self::GammaRepr::*; -use crate::normal::StandardNormal; -use crate::{Distribution, Exp, Exp1, Open01}; +use crate::{Distribution, Exp, Exp1, Open01, StandardNormal}; use core::fmt; use num_traits::Float; use rand::Rng; @@ -80,7 +74,7 @@ where repr: GammaRepr, } -/// Error type returned from `Gamma::new`. +/// Error type returned from [`Gamma::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `shape <= 0` or `nan`. @@ -276,632 +270,12 @@ where } } -/// The [chi-squared distribution](https://en.wikipedia.org/wiki/Chi-squared_distribution) `χ²(k)`. -/// -/// The chi-squared distribution is a continuous probability -/// distribution with parameter `k > 0` degrees of freedom. -/// -/// For `k > 0` integral, this distribution is the sum of the squares -/// of `k` independent standard normal random variables. For other -/// `k`, this uses the equivalent characterisation -/// `χ²(k) = Gamma(k/2, 2)`. -/// -/// # Plot -/// -/// The plot shows the chi-squared distribution with various degrees -/// of freedom. -/// -/// ![Chi-squared distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/chi_squared.svg) -/// -/// # Example -/// -/// ``` -/// use rand_distr::{ChiSquared, Distribution}; -/// -/// let chi = ChiSquared::new(11.0).unwrap(); -/// let v = chi.sample(&mut rand::thread_rng()); -/// println!("{} is from a χ²(11) distribution", v) -/// ``` -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct ChiSquared -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - repr: ChiSquaredRepr, -} - -/// Error type returned from `ChiSquared::new` and `StudentT::new`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum ChiSquaredError { - /// `0.5 * k <= 0` or `nan`. - DoFTooSmall, -} - -impl fmt::Display for ChiSquaredError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - ChiSquaredError::DoFTooSmall => { - "degrees-of-freedom k is not positive in chi-squared distribution" - } - }) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ChiSquaredError {} - -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -enum ChiSquaredRepr -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - // k == 1, Gamma(alpha, ..) is particularly slow for alpha < 1, - // e.g. when alpha = 1/2 as it would be for this case, so special- - // casing and using the definition of N(0,1)^2 is faster. - DoFExactlyOne, - DoFAnythingElse(Gamma), -} - -impl ChiSquared -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - /// Create a new chi-squared distribution with degrees-of-freedom - /// `k`. - pub fn new(k: F) -> Result, ChiSquaredError> { - let repr = if k == F::one() { - DoFExactlyOne - } else { - if !(F::from(0.5).unwrap() * k > F::zero()) { - return Err(ChiSquaredError::DoFTooSmall); - } - DoFAnythingElse(Gamma::new(F::from(0.5).unwrap() * k, F::from(2.0).unwrap()).unwrap()) - }; - Ok(ChiSquared { repr }) - } -} -impl Distribution for ChiSquared -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - fn sample(&self, rng: &mut R) -> F { - match self.repr { - DoFExactlyOne => { - // k == 1 => N(0,1)^2 - let norm: F = rng.sample(StandardNormal); - norm * norm - } - DoFAnythingElse(ref g) => g.sample(rng), - } - } -} - -/// The [Fisher F-distribution](https://en.wikipedia.org/wiki/F-distribution) `F(m, n)`. -/// -/// This distribution is equivalent to the ratio of two normalised -/// chi-squared distributions, that is, `F(m,n) = (χ²(m)/m) / -/// (χ²(n)/n)`. -/// -/// # Plot -/// -/// The plot shows the F-distribution with various values of `m` and `n`. -/// -/// ![F-distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/fisher_f.svg) -/// -/// # Example -/// -/// ``` -/// use rand_distr::{FisherF, Distribution}; -/// -/// let f = FisherF::new(2.0, 32.0).unwrap(); -/// let v = f.sample(&mut rand::thread_rng()); -/// println!("{} is from an F(2, 32) distribution", v) -/// ``` -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct FisherF -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - numer: ChiSquared, - denom: ChiSquared, - // denom_dof / numer_dof so that this can just be a straight - // multiplication, rather than a division. - dof_ratio: F, -} - -/// Error type returned from `FisherF::new`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum FisherFError { - /// `m <= 0` or `nan`. - MTooSmall, - /// `n <= 0` or `nan`. - NTooSmall, -} - -impl fmt::Display for FisherFError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - FisherFError::MTooSmall => "m is not positive in Fisher F distribution", - FisherFError::NTooSmall => "n is not positive in Fisher F distribution", - }) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for FisherFError {} - -impl FisherF -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - /// Create a new `FisherF` distribution, with the given parameter. - pub fn new(m: F, n: F) -> Result, FisherFError> { - let zero = F::zero(); - if !(m > zero) { - return Err(FisherFError::MTooSmall); - } - if !(n > zero) { - return Err(FisherFError::NTooSmall); - } - - Ok(FisherF { - numer: ChiSquared::new(m).unwrap(), - denom: ChiSquared::new(n).unwrap(), - dof_ratio: n / m, - }) - } -} -impl Distribution for FisherF -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - fn sample(&self, rng: &mut R) -> F { - self.numer.sample(rng) / self.denom.sample(rng) * self.dof_ratio - } -} - -/// The [Student t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) `t(ν)`. -/// -/// The t-distribution is a continuous probability distribution -/// parameterized by degrees of freedom `ν` (`nu`), which -/// arises when estimating the mean of a normally-distributed -/// population in situations where the sample size is small and -/// the population's standard deviation is unknown. -/// It is widely used in hypothesis testing. -/// -/// For `ν = 1`, this is equivalent to the standard -/// [`Cauchy`](crate::Cauchy) distribution, -/// and as `ν` diverges to infinity, `t(ν)` converges to -/// [`StandardNormal`](crate::StandardNormal). -/// -/// # Plot -/// -/// The plot shows the t-distribution with various degrees of freedom. -/// -/// ![T-distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/student_t.svg) -/// -/// # Example -/// -/// ``` -/// use rand_distr::{StudentT, Distribution}; -/// -/// let t = StudentT::new(11.0).unwrap(); -/// let v = t.sample(&mut rand::thread_rng()); -/// println!("{} is from a t(11) distribution", v) -/// ``` -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct StudentT -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - chi: ChiSquared, - dof: F, -} - -impl StudentT -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - /// Create a new Student t-distribution with `ν` (nu) - /// degrees of freedom. - pub fn new(nu: F) -> Result, ChiSquaredError> { - Ok(StudentT { - chi: ChiSquared::new(nu)?, - dof: nu, - }) - } -} -impl Distribution for StudentT -where - F: Float, - StandardNormal: Distribution, - Exp1: Distribution, - Open01: Distribution, -{ - fn sample(&self, rng: &mut R) -> F { - let norm: F = rng.sample(StandardNormal); - norm * (self.dof / self.chi.sample(rng)).sqrt() - } -} - -/// The algorithm used for sampling the Beta distribution. -/// -/// Reference: -/// -/// R. C. H. Cheng (1978). -/// Generating beta variates with nonintegral shape parameters. -/// Communications of the ACM 21, 317-322. -/// https://doi.org/10.1145/359460.359482 -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -enum BetaAlgorithm { - BB(BB), - BC(BC), -} - -/// Algorithm BB for `min(alpha, beta) > 1`. -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -struct BB { - alpha: N, - beta: N, - gamma: N, -} - -/// Algorithm BC for `min(alpha, beta) <= 1`. -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -struct BC { - alpha: N, - beta: N, - kappa1: N, - kappa2: N, -} - -/// The [Beta distribution](https://en.wikipedia.org/wiki/Beta_distribution) `Beta(α, β)`. -/// -/// The Beta distribution is a continuous probability distribution -/// defined on the interval `[0, 1]`. It is the conjugate prior for the -/// parameter `p` of the [`Binomial`][crate::Binomial] distribution. -/// -/// It has two shape parameters `α` (alpha) and `β` (beta) which control -/// the shape of the distribution. Both `a` and `β` must be greater than zero. -/// The distribution is symmetric when `α = β`. -/// -/// # Plot -/// -/// The plot shows the Beta distribution with various combinations -/// of `α` and `β`. -/// -/// ![Beta distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/beta.svg) -/// -/// # Example -/// -/// ``` -/// use rand_distr::{Distribution, Beta}; -/// -/// let beta = Beta::new(2.0, 5.0).unwrap(); -/// let v = beta.sample(&mut rand::thread_rng()); -/// println!("{} is from a Beta(2, 5) distribution", v); -/// ``` -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct Beta -where - F: Float, - Open01: Distribution, -{ - a: F, - b: F, - switched_params: bool, - algorithm: BetaAlgorithm, -} - -/// Error type returned from `Beta::new`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub enum BetaError { - /// `alpha <= 0` or `nan`. - AlphaTooSmall, - /// `beta <= 0` or `nan`. - BetaTooSmall, -} - -impl fmt::Display for BetaError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - BetaError::AlphaTooSmall => "alpha is not positive in beta distribution", - BetaError::BetaTooSmall => "beta is not positive in beta distribution", - }) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for BetaError {} - -impl Beta -where - F: Float, - Open01: Distribution, -{ - /// Construct an object representing the `Beta(alpha, beta)` - /// distribution. - pub fn new(alpha: F, beta: F) -> Result, BetaError> { - if !(alpha > F::zero()) { - return Err(BetaError::AlphaTooSmall); - } - if !(beta > F::zero()) { - return Err(BetaError::BetaTooSmall); - } - // From now on, we use the notation from the reference, - // i.e. `alpha` and `beta` are renamed to `a0` and `b0`. - let (a0, b0) = (alpha, beta); - let (a, b, switched_params) = if a0 < b0 { - (a0, b0, false) - } else { - (b0, a0, true) - }; - if a > F::one() { - // Algorithm BB - let alpha = a + b; - - let two = F::from(2.).unwrap(); - let beta_numer = alpha - two; - let beta_denom = two * a * b - alpha; - let beta = (beta_numer / beta_denom).sqrt(); - - let gamma = a + F::one() / beta; - - Ok(Beta { - a, - b, - switched_params, - algorithm: BetaAlgorithm::BB(BB { alpha, beta, gamma }), - }) - } else { - // Algorithm BC - // - // Here `a` is the maximum instead of the minimum. - let (a, b, switched_params) = (b, a, !switched_params); - let alpha = a + b; - let beta = F::one() / b; - let delta = F::one() + a - b; - let kappa1 = delta - * (F::from(1. / 18. / 4.).unwrap() + F::from(3. / 18. / 4.).unwrap() * b) - / (a * beta - F::from(14. / 18.).unwrap()); - let kappa2 = F::from(0.25).unwrap() - + (F::from(0.5).unwrap() + F::from(0.25).unwrap() / delta) * b; - - Ok(Beta { - a, - b, - switched_params, - algorithm: BetaAlgorithm::BC(BC { - alpha, - beta, - kappa1, - kappa2, - }), - }) - } - } -} - -impl Distribution for Beta -where - F: Float, - Open01: Distribution, -{ - fn sample(&self, rng: &mut R) -> F { - let mut w; - match self.algorithm { - BetaAlgorithm::BB(algo) => { - loop { - // 1. - let u1 = rng.sample(Open01); - let u2 = rng.sample(Open01); - let v = algo.beta * (u1 / (F::one() - u1)).ln(); - w = self.a * v.exp(); - let z = u1 * u1 * u2; - let r = algo.gamma * v - F::from(4.).unwrap().ln(); - let s = self.a + r - w; - // 2. - if s + F::one() + F::from(5.).unwrap().ln() >= F::from(5.).unwrap() * z { - break; - } - // 3. - let t = z.ln(); - if s >= t { - break; - } - // 4. - if !(r + algo.alpha * (algo.alpha / (self.b + w)).ln() < t) { - break; - } - } - } - BetaAlgorithm::BC(algo) => { - loop { - let z; - // 1. - let u1 = rng.sample(Open01); - let u2 = rng.sample(Open01); - if u1 < F::from(0.5).unwrap() { - // 2. - let y = u1 * u2; - z = u1 * y; - if F::from(0.25).unwrap() * u2 + z - y >= algo.kappa1 { - continue; - } - } else { - // 3. - z = u1 * u1 * u2; - if z <= F::from(0.25).unwrap() { - let v = algo.beta * (u1 / (F::one() - u1)).ln(); - w = self.a * v.exp(); - break; - } - // 4. - if z >= algo.kappa2 { - continue; - } - } - // 5. - let v = algo.beta * (u1 / (F::one() - u1)).ln(); - w = self.a * v.exp(); - if !(algo.alpha * ((algo.alpha / (self.b + w)).ln() + v) - - F::from(4.).unwrap().ln() - < z.ln()) - { - break; - }; - } - } - }; - // 5. for BB, 6. for BC - if !self.switched_params { - if w == F::infinity() { - // Assuming `b` is finite, for large `w`: - return F::one(); - } - w / (self.b + w) - } else { - self.b / (self.b + w) - } - } -} - #[cfg(test)] mod test { use super::*; - #[test] - fn test_chi_squared_one() { - let chi = ChiSquared::new(1.0).unwrap(); - let mut rng = crate::test::rng(201); - for _ in 0..1000 { - chi.sample(&mut rng); - } - } - #[test] - fn test_chi_squared_small() { - let chi = ChiSquared::new(0.5).unwrap(); - let mut rng = crate::test::rng(202); - for _ in 0..1000 { - chi.sample(&mut rng); - } - } - #[test] - fn test_chi_squared_large() { - let chi = ChiSquared::new(30.0).unwrap(); - let mut rng = crate::test::rng(203); - for _ in 0..1000 { - chi.sample(&mut rng); - } - } - #[test] - #[should_panic] - fn test_chi_squared_invalid_dof() { - ChiSquared::new(-1.0).unwrap(); - } - - #[test] - fn test_f() { - let f = FisherF::new(2.0, 32.0).unwrap(); - let mut rng = crate::test::rng(204); - for _ in 0..1000 { - f.sample(&mut rng); - } - } - - #[test] - fn test_t() { - let t = StudentT::new(11.0).unwrap(); - let mut rng = crate::test::rng(205); - for _ in 0..1000 { - t.sample(&mut rng); - } - } - - #[test] - fn test_beta() { - let beta = Beta::new(1.0, 2.0).unwrap(); - let mut rng = crate::test::rng(201); - for _ in 0..1000 { - beta.sample(&mut rng); - } - } - - #[test] - #[should_panic] - fn test_beta_invalid_dof() { - Beta::new(0., 0.).unwrap(); - } - - #[test] - fn test_beta_small_param() { - let beta = Beta::::new(1e-3, 1e-3).unwrap(); - let mut rng = crate::test::rng(206); - for i in 0..1000 { - assert!(!beta.sample(&mut rng).is_nan(), "failed at i={}", i); - } - } - #[test] fn gamma_distributions_can_be_compared() { assert_eq!(Gamma::new(1.0, 2.0), Gamma::new(1.0, 2.0)); } - - #[test] - fn beta_distributions_can_be_compared() { - assert_eq!(Beta::new(1.0, 2.0), Beta::new(1.0, 2.0)); - } - - #[test] - fn chi_squared_distributions_can_be_compared() { - assert_eq!(ChiSquared::new(1.0), ChiSquared::new(1.0)); - } - - #[test] - fn fisher_f_distributions_can_be_compared() { - assert_eq!(FisherF::new(1.0, 2.0), FisherF::new(1.0, 2.0)); - } - - #[test] - fn student_t_distributions_can_be_compared() { - assert_eq!(StudentT::new(1.0), StudentT::new(1.0)); - } } diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index e54496d8e5d..9beb9382281 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -46,7 +46,7 @@ pub struct Geometric { k: u64, } -/// Error type returned from `Geometric::new`. +/// Error type returned from [`Geometric::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `p < 0 || p > 1` or `nan` diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index fd9324acf2b..6a7f1ae7b94 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -52,7 +52,7 @@ where scale: F, } -/// Error type returned from `Gumbel::new`. +/// Error type returned from [`Gumbel::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// location is infinite or NaN diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index c15b143b626..4e4f4306353 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -68,7 +68,7 @@ pub struct Hypergeometric { sampling_method: SamplingMethod, } -/// Error type returned from `Hypergeometric::new`. +/// Error type returned from [`Hypergeometric::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `total_population_size` is too large, causing floating point underflow. diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 1039f6045ba..4a5aad7982c 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -5,7 +5,7 @@ use core::fmt; use num_traits::Float; use rand::Rng; -/// Error type returned from `InverseGaussian::new` +/// Error type returned from [`InverseGaussian::new`] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { /// `mean <= 0` or `nan`. diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 2394f54997f..a3852256516 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -98,16 +98,16 @@ pub use rand::distributions::{ Standard, Uniform, }; +pub use self::beta::{Beta, Error as BetaError}; pub use self::binomial::{Binomial, Error as BinomialError}; pub use self::cauchy::{Cauchy, Error as CauchyError}; +pub use self::chi_squared::{ChiSquared, Error as ChiSquaredError}; #[cfg(feature = "alloc")] pub use self::dirichlet::{Dirichlet, Error as DirichletError}; pub use self::exponential::{Error as ExpError, Exp, Exp1}; +pub use self::fisher_f::{Error as FisherFError, FisherF}; pub use self::frechet::{Error as FrechetError, Frechet}; -pub use self::gamma::{ - Beta, BetaError, ChiSquared, ChiSquaredError, Error as GammaError, FisherF, FisherFError, - Gamma, StudentT, -}; +pub use self::gamma::{Error as GammaError, Gamma}; pub use self::geometric::{Error as GeoError, Geometric, StandardGeometric}; pub use self::gumbel::{Error as GumbelError, Gumbel}; pub use self::hypergeometric::{Error as HyperGeoError, Hypergeometric}; @@ -126,9 +126,11 @@ pub use self::unit_circle::UnitCircle; pub use self::unit_disc::UnitDisc; pub use self::unit_sphere::UnitSphere; pub use self::weibull::{Error as WeibullError, Weibull}; -pub use self::zipf::{Zeta, ZetaError, Zipf, ZipfError}; +pub use self::zeta::{Error as ZetaError, Zeta}; +pub use self::zipf::{Error as ZipfError, Zipf}; #[cfg(feature = "alloc")] pub use rand::distributions::{WeightError, WeightedIndex}; +pub use student_t::StudentT; #[cfg(feature = "alloc")] pub use weighted_alias::WeightedAliasIndex; #[cfg(feature = "alloc")] @@ -192,10 +194,13 @@ pub mod weighted_alias; #[cfg(feature = "alloc")] pub mod weighted_tree; +mod beta; mod binomial; mod cauchy; +mod chi_squared; mod dirichlet; mod exponential; +mod fisher_f; mod frechet; mod gamma; mod geometric; @@ -208,6 +213,7 @@ mod pareto; mod pert; mod poisson; mod skew_normal; +mod student_t; mod triangular; mod unit_ball; mod unit_circle; @@ -215,5 +221,6 @@ mod unit_disc; mod unit_sphere; mod utils; mod weibull; +mod zeta; mod ziggurat_tables; mod zipf; diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 1b698ec4bb8..f6e6adc4206 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -153,7 +153,7 @@ where std_dev: F, } -/// Error type returned from `Normal::new` and `LogNormal::new`. +/// Error type returned from [`Normal::new`] and [`LogNormal::new`](crate::LogNormal::new). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// The mean value is too small (log-normal samples must be positive) diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index f8f62170f59..2c7fe71a31e 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -3,7 +3,7 @@ use core::fmt; use num_traits::Float; use rand::Rng; -/// Error type returned from `NormalInverseGaussian::new` +/// Error type returned from [`NormalInverseGaussian::new`] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Error { /// `alpha <= 0` or `nan`. diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index ba0465f7e65..f8b86c7035c 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -46,7 +46,7 @@ where inv_neg_shape: F, } -/// Error type returned from `Pareto::new`. +/// Error type returned from [`Pareto::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `scale <= 0` or `nan`. diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index c84d4dce35e..78675ad9c41 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -54,7 +54,7 @@ where magic_val: F, } -/// Error type returned from `Poisson::new`. +/// Error type returned from [`Poisson::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `lambda <= 0` diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 6ef521be25f..8ef88428a4e 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -68,7 +68,7 @@ where shape: F, } -/// Error type returned from `SkewNormal::new`. +/// Error type returned from [`SkewNormal::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// The scale parameter is not finite or it is less or equal to zero. diff --git a/rand_distr/src/student_t.rs b/rand_distr/src/student_t.rs new file mode 100644 index 00000000000..86a5fb5b456 --- /dev/null +++ b/rand_distr/src/student_t.rs @@ -0,0 +1,107 @@ +// Copyright 2018 Developers of the Rand project. +// Copyright 2013 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Student's t-distribution. + +use crate::{ChiSquared, ChiSquaredError}; +use crate::{Distribution, Exp1, Open01, StandardNormal}; +use num_traits::Float; +use rand::Rng; +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// The [Student t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) `t(ν)`. +/// +/// The t-distribution is a continuous probability distribution +/// parameterized by degrees of freedom `ν` (`nu`), which +/// arises when estimating the mean of a normally-distributed +/// population in situations where the sample size is small and +/// the population's standard deviation is unknown. +/// It is widely used in hypothesis testing. +/// +/// For `ν = 1`, this is equivalent to the standard +/// [`Cauchy`](crate::Cauchy) distribution, +/// and as `ν` diverges to infinity, `t(ν)` converges to +/// [`StandardNormal`](crate::StandardNormal). +/// +/// # Plot +/// +/// The plot shows the t-distribution with various degrees of freedom. +/// +/// ![T-distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/student_t.svg) +/// +/// # Example +/// +/// ``` +/// use rand_distr::{StudentT, Distribution}; +/// +/// let t = StudentT::new(11.0).unwrap(); +/// let v = t.sample(&mut rand::thread_rng()); +/// println!("{} is from a t(11) distribution", v) +/// ``` +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct StudentT +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + chi: ChiSquared, + dof: F, +} + +impl StudentT +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + /// Create a new Student t-distribution with `ν` (nu) + /// degrees of freedom. + pub fn new(nu: F) -> Result, ChiSquaredError> { + Ok(StudentT { + chi: ChiSquared::new(nu)?, + dof: nu, + }) + } +} +impl Distribution for StudentT +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + fn sample(&self, rng: &mut R) -> F { + let norm: F = rng.sample(StandardNormal); + norm * (self.dof / self.chi.sample(rng)).sqrt() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_t() { + let t = StudentT::new(11.0).unwrap(); + let mut rng = crate::test::rng(205); + for _ in 0..1000 { + t.sample(&mut rng); + } + } + + #[test] + fn student_t_distributions_can_be_compared() { + assert_eq!(StudentT::new(1.0), StudentT::new(1.0)); + } +} diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index e6f80736a4b..145a4df3883 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -44,7 +44,7 @@ where scale: F, } -/// Error type returned from `Weibull::new`. +/// Error type returned from [`Weibull::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { /// `scale <= 0` or `nan`. diff --git a/rand_distr/src/zeta.rs b/rand_distr/src/zeta.rs new file mode 100644 index 00000000000..da146883f0a --- /dev/null +++ b/rand_distr/src/zeta.rs @@ -0,0 +1,192 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Zeta distribution. + +use crate::{Distribution, Standard}; +use core::fmt; +use num_traits::Float; +use rand::{distributions::OpenClosed01, Rng}; + +/// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) `Zeta(s)`. +/// +/// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) +/// is a discrete probability distribution with parameter `s`. +/// It is a special case of the [`Zipf`](crate::Zipf) distribution with `n = ∞`. +/// It is also known as the discrete Pareto, Riemann-Zeta, Zipf, or Zipf–Estoup distribution. +/// +/// # Density function +/// +/// `f(k) = k^(-s) / ζ(s)` for `k >= 1`, where `ζ` is the +/// [Riemann zeta function](https://en.wikipedia.org/wiki/Riemann_zeta_function). +/// +/// # Plot +/// +/// The following plot illustrates the zeta distribution for various values of `s`. +/// +/// ![Zeta distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/zeta.svg) +/// +/// # Example +/// ``` +/// use rand::prelude::*; +/// use rand_distr::Zeta; +/// +/// let val: f64 = thread_rng().sample(Zeta::new(1.5).unwrap()); +/// println!("{}", val); +/// ``` +/// +/// # Notes +/// +/// The zeta distribution has no upper limit. Sampled values may be infinite. +/// In particular, a value of infinity might be returned for the following +/// reasons: +/// 1. it is the best representation in the type `F` of the actual sample. +/// 2. to prevent infinite loops for very small `s`. +/// +/// # Implementation details +/// +/// We are using the algorithm from +/// [Non-Uniform Random Variate Generation](https://doi.org/10.1007/978-1-4613-8643-8), +/// Section 6.1, page 551. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Zeta +where + F: Float, + Standard: Distribution, + OpenClosed01: Distribution, +{ + s_minus_1: F, + b: F, +} + +/// Error type returned from [`Zeta::new`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// `s <= 1` or `nan`. + STooSmall, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::STooSmall => "s <= 1 or is NaN in Zeta distribution", + }) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl Zeta +where + F: Float, + Standard: Distribution, + OpenClosed01: Distribution, +{ + /// Construct a new `Zeta` distribution with given `s` parameter. + #[inline] + pub fn new(s: F) -> Result, Error> { + if !(s > F::one()) { + return Err(Error::STooSmall); + } + let s_minus_1 = s - F::one(); + let two = F::one() + F::one(); + Ok(Zeta { + s_minus_1, + b: two.powf(s_minus_1), + }) + } +} + +impl Distribution for Zeta +where + F: Float, + Standard: Distribution, + OpenClosed01: Distribution, +{ + #[inline] + fn sample(&self, rng: &mut R) -> F { + loop { + let u = rng.sample(OpenClosed01); + let x = u.powf(-F::one() / self.s_minus_1).floor(); + debug_assert!(x >= F::one()); + if x.is_infinite() { + // For sufficiently small `s`, `x` will always be infinite, + // which is rejected, resulting in an infinite loop. We avoid + // this by always returning infinity instead. + return x; + } + + let t = (F::one() + F::one() / x).powf(self.s_minus_1); + + let v = rng.sample(Standard); + if v * x * (t - F::one()) * self.b <= t * (self.b - F::one()) { + return x; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_samples>(distr: D, zero: F, expected: &[F]) { + let mut rng = crate::test::rng(213); + let mut buf = [zero; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + #[test] + #[should_panic] + fn zeta_invalid() { + Zeta::new(1.).unwrap(); + } + + #[test] + #[should_panic] + fn zeta_nan() { + Zeta::new(f64::NAN).unwrap(); + } + + #[test] + fn zeta_sample() { + let a = 2.0; + let d = Zeta::new(a).unwrap(); + let mut rng = crate::test::rng(1); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 1.); + } + } + + #[test] + fn zeta_small_a() { + let a = 1. + 1e-15; + let d = Zeta::new(a).unwrap(); + let mut rng = crate::test::rng(2); + for _ in 0..1000 { + let r = d.sample(&mut rng); + assert!(r >= 1.); + } + } + + #[test] + fn zeta_value_stability() { + test_samples(Zeta::new(1.5).unwrap(), 0f32, &[1.0, 2.0, 1.0, 1.0]); + test_samples(Zeta::new(2.0).unwrap(), 0f64, &[2.0, 1.0, 1.0, 1.0]); + } + + #[test] + fn zeta_distributions_can_be_compared() { + assert_eq!(Zeta::new(1.0), Zeta::new(1.0)); + } +} diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index c8e3fef9b39..70bb891aa7f 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -6,131 +6,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The Zeta and related distributions. +//! The Zipf distribution. use crate::{Distribution, Standard}; use core::fmt; use num_traits::Float; -use rand::{distributions::OpenClosed01, Rng}; - -/// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) `Zeta(s)`. -/// -/// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) -/// is a discrete probability distribution with parameter `s`. -/// It is a special case of the [`Zipf`] distribution with `n = ∞`. -/// It is also known as the discrete Pareto, Riemann-Zeta, Zipf, or Zipf–Estoup distribution. -/// -/// # Density function -/// -/// `f(k) = k^(-s) / ζ(s)` for `k >= 1`, where `ζ` is the -/// [Riemann zeta function](https://en.wikipedia.org/wiki/Riemann_zeta_function). -/// -/// # Plot -/// -/// The following plot illustrates the zeta distribution for various values of `s`. -/// -/// ![Zeta distribution](https://raw.githubusercontent.com/rust-random/charts/main/charts/zeta.svg) -/// -/// # Example -/// ``` -/// use rand::prelude::*; -/// use rand_distr::Zeta; -/// -/// let val: f64 = thread_rng().sample(Zeta::new(1.5).unwrap()); -/// println!("{}", val); -/// ``` -/// -/// # Notes -/// -/// The zeta distribution has no upper limit. Sampled values may be infinite. -/// In particular, a value of infinity might be returned for the following -/// reasons: -/// 1. it is the best representation in the type `F` of the actual sample. -/// 2. to prevent infinite loops for very small `s`. -/// -/// # Implementation details -/// -/// We are using the algorithm from -/// [Non-Uniform Random Variate Generation](https://doi.org/10.1007/978-1-4613-8643-8), -/// Section 6.1, page 551. -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Zeta -where - F: Float, - Standard: Distribution, - OpenClosed01: Distribution, -{ - s_minus_1: F, - b: F, -} - -/// Error type returned from `Zeta::new`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ZetaError { - /// `s <= 1` or `nan`. - STooSmall, -} - -impl fmt::Display for ZetaError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - ZetaError::STooSmall => "s <= 1 or is NaN in Zeta distribution", - }) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ZetaError {} - -impl Zeta -where - F: Float, - Standard: Distribution, - OpenClosed01: Distribution, -{ - /// Construct a new `Zeta` distribution with given `s` parameter. - #[inline] - pub fn new(s: F) -> Result, ZetaError> { - if !(s > F::one()) { - return Err(ZetaError::STooSmall); - } - let s_minus_1 = s - F::one(); - let two = F::one() + F::one(); - Ok(Zeta { - s_minus_1, - b: two.powf(s_minus_1), - }) - } -} - -impl Distribution for Zeta -where - F: Float, - Standard: Distribution, - OpenClosed01: Distribution, -{ - #[inline] - fn sample(&self, rng: &mut R) -> F { - loop { - let u = rng.sample(OpenClosed01); - let x = u.powf(-F::one() / self.s_minus_1).floor(); - debug_assert!(x >= F::one()); - if x.is_infinite() { - // For sufficiently small `s`, `x` will always be infinite, - // which is rejected, resulting in an infinite loop. We avoid - // this by always returning infinity instead. - return x; - } - - let t = (F::one() + F::one() / x).powf(self.s_minus_1); - - let v = rng.sample(Standard); - if v * x * (t - F::one()) * self.b <= t * (self.b - F::one()) { - return x; - } - } - } -} +use rand::Rng; /// The Zipf (Zipfian) distribution `Zipf(n, s)`. /// @@ -175,26 +56,26 @@ where q: F, } -/// Error type returned from `Zipf::new`. +/// Error type returned from [`Zipf::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ZipfError { +pub enum Error { /// `s < 0` or `nan`. STooSmall, /// `n < 1`. NTooSmall, } -impl fmt::Display for ZipfError { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - ZipfError::STooSmall => "s < 0 or is NaN in Zipf distribution", - ZipfError::NTooSmall => "n < 1 in Zipf distribution", + Error::STooSmall => "s < 0 or is NaN in Zipf distribution", + Error::NTooSmall => "n < 1 in Zipf distribution", }) } } #[cfg(feature = "std")] -impl std::error::Error for ZipfError {} +impl std::error::Error for Error {} impl Zipf where @@ -206,12 +87,12 @@ where /// /// For large `n`, rounding may occur to fit the number into the float type. #[inline] - pub fn new(n: u64, s: F) -> Result, ZipfError> { + pub fn new(n: u64, s: F) -> Result, Error> { if !(s >= F::zero()) { - return Err(ZipfError::STooSmall); + return Err(Error::STooSmall); } if n < 1 { - return Err(ZipfError::NTooSmall); + return Err(Error::NTooSmall); } let n = F::from(n).unwrap(); // This does not fail. let q = if s != F::one() { @@ -282,46 +163,6 @@ mod tests { assert_eq!(buf, expected); } - #[test] - #[should_panic] - fn zeta_invalid() { - Zeta::new(1.).unwrap(); - } - - #[test] - #[should_panic] - fn zeta_nan() { - Zeta::new(f64::NAN).unwrap(); - } - - #[test] - fn zeta_sample() { - let a = 2.0; - let d = Zeta::new(a).unwrap(); - let mut rng = crate::test::rng(1); - for _ in 0..1000 { - let r = d.sample(&mut rng); - assert!(r >= 1.); - } - } - - #[test] - fn zeta_small_a() { - let a = 1. + 1e-15; - let d = Zeta::new(a).unwrap(); - let mut rng = crate::test::rng(2); - for _ in 0..1000 { - let r = d.sample(&mut rng); - assert!(r >= 1.); - } - } - - #[test] - fn zeta_value_stability() { - test_samples(Zeta::new(1.5).unwrap(), 0f32, &[1.0, 2.0, 1.0, 1.0]); - test_samples(Zeta::new(2.0).unwrap(), 0f64, &[2.0, 1.0, 1.0, 1.0]); - } - #[test] #[should_panic] fn zipf_s_too_small() { @@ -392,9 +233,4 @@ mod tests { fn zipf_distributions_can_be_compared() { assert_eq!(Zipf::new(1, 2.0), Zipf::new(1, 2.0)); } - - #[test] - fn zeta_distributions_can_be_compared() { - assert_eq!(Zeta::new(1.0), Zeta::new(1.0)); - } } diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index 80453496735..e49b415fea9 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -75,7 +75,7 @@ const ALWAYS_TRUE: u64 = u64::MAX; // in `no_std` mode. const SCALE: f64 = 2.0 * (1u64 << 63) as f64; -/// Error type returned from `Bernoulli::new`. +/// Error type returned from [`Bernoulli::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BernoulliError { /// `p < 0` or `p > 1`. diff --git a/src/distributions/weighted_index.rs b/src/distributions/weighted_index.rs index 8a887ce3e85..88fad5a88ad 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distributions/weighted_index.rs @@ -702,7 +702,7 @@ mod test { } } -/// Errors returned by weighted distributions +/// Errors returned by [`WeightedIndex::new`], [`WeightedIndex::update_weights`] and other weighted distributions #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WeightError { /// The input weight sequence is empty, too long, or wrongly ordered From 2584f48ace692d1cb93c73a10fff94daf9e1ceee Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Jul 2024 09:04:49 +0100 Subject: [PATCH 394/443] Fix pert for mode approx eq mean; use builder pattern (#1452) - Fix #1311 (mode close to mean) - Use a builder pattern, allowing specification via mode OR mean --- rand_distr/CHANGELOG.md | 2 +- rand_distr/src/lib.rs | 2 +- rand_distr/src/pert.rs | 105 ++++++++++++++++++++++------ rand_distr/tests/value_stability.rs | 2 +- 4 files changed, 86 insertions(+), 25 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index cab7591505d..570474bee44 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -5,9 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased - ### Added - Add plots for `rand_distr` distributions to documentation (#1434) +- Add `PertBuilder`, fix case where mode ≅ mean (#1452) ## [0.5.0-alpha.1] - 2024-03-18 - Target `rand` version `0.9.0-alpha.1` diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index a3852256516..f6f3ad54cfa 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -117,7 +117,7 @@ pub use self::normal_inverse_gaussian::{ Error as NormalInverseGaussianError, NormalInverseGaussian, }; pub use self::pareto::{Error as ParetoError, Pareto}; -pub use self::pert::{Pert, PertError}; +pub use self::pert::{Pert, PertBuilder, PertError}; pub use self::poisson::{Error as PoissonError, Poisson}; pub use self::skew_normal::{Error as SkewNormalError, SkewNormal}; pub use self::triangular::{Triangular, TriangularError}; diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index df5361d7049..ae268dad7b5 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -31,7 +31,7 @@ use rand::Rng; /// ```rust /// use rand_distr::{Pert, Distribution}; /// -/// let d = Pert::new(0., 5., 2.5).unwrap(); +/// let d = Pert::new(0., 5.).with_mode(2.5).unwrap(); /// let v = d.sample(&mut rand::thread_rng()); /// println!("{} is from a PERT distribution", v); /// ``` @@ -82,35 +82,75 @@ where Exp1: Distribution, Open01: Distribution, { - /// Set up the PERT distribution with defined `min`, `max` and `mode`. + /// Construct a PERT distribution with defined `min`, `max` /// - /// This is equivalent to calling `Pert::new_with_shape` with `shape == 4.0`. + /// # Example + /// + /// ``` + /// use rand_distr::Pert; + /// let pert_dist = Pert::new(0.0, 10.0) + /// .with_shape(3.5) + /// .with_mean(3.0) + /// .unwrap(); + /// # let _unused: Pert = pert_dist; + /// ``` + #[allow(clippy::new_ret_no_self)] + #[inline] + pub fn new(min: F, max: F) -> PertBuilder { + let shape = F::from(4.0).unwrap(); + PertBuilder { min, max, shape } + } +} + +/// Struct used to build a [`Pert`] +#[derive(Debug)] +pub struct PertBuilder { + min: F, + max: F, + shape: F, +} + +impl PertBuilder +where + F: Float, + StandardNormal: Distribution, + Exp1: Distribution, + Open01: Distribution, +{ + /// Set the shape parameter + /// + /// If not specified, this defaults to 4. + #[inline] + pub fn with_shape(mut self, shape: F) -> PertBuilder { + self.shape = shape; + self + } + + /// Specify the mean #[inline] - pub fn new(min: F, max: F, mode: F) -> Result, PertError> { - Pert::new_with_shape(min, max, mode, F::from(4.).unwrap()) + pub fn with_mean(self, mean: F) -> Result, PertError> { + let two = F::from(2.0).unwrap(); + let mode = ((self.shape + two) * mean - self.min - self.max) / self.shape; + self.with_mode(mode) } - /// Set up the PERT distribution with defined `min`, `max`, `mode` and - /// `shape`. - pub fn new_with_shape(min: F, max: F, mode: F, shape: F) -> Result, PertError> { - if !(max > min) { + /// Specify the mode + #[inline] + pub fn with_mode(self, mode: F) -> Result, PertError> { + if !(self.max > self.min) { return Err(PertError::RangeTooSmall); } - if !(mode >= min && max >= mode) { + if !(mode >= self.min && self.max >= mode) { return Err(PertError::ModeRange); } - if !(shape >= F::from(0.).unwrap()) { + if !(self.shape >= F::from(0.).unwrap()) { return Err(PertError::ShapeTooSmall); } + let (min, max, shape) = (self.min, self.max, self.shape); let range = max - min; - let mu = (min + max + shape * mode) / (shape + F::from(2.).unwrap()); - let v = if mu == mode { - shape * F::from(0.5).unwrap() + F::from(1.).unwrap() - } else { - (mu - min) * (F::from(2.).unwrap() * mode - min - max) / ((mode - mu) * (max - min)) - }; - let w = v * (max - mu) / (mu - min); + let v = F::from(1.0).unwrap() + shape * (mode - min) / range; + let w = F::from(1.0).unwrap() + shape * (max - mode) / range; let beta = Beta::new(v, w).map_err(|_| PertError::RangeTooSmall)?; Ok(Pert { min, range, beta }) } @@ -136,17 +176,38 @@ mod test { #[test] fn test_pert() { for &(min, max, mode) in &[(-1., 1., 0.), (1., 2., 1.), (5., 25., 25.)] { - let _distr = Pert::new(min, max, mode).unwrap(); + let _distr = Pert::new(min, max).with_mode(mode).unwrap(); // TODO: test correctness } for &(min, max, mode) in &[(-1., 1., 2.), (-1., 1., -2.), (2., 1., 1.)] { - assert!(Pert::new(min, max, mode).is_err()); + assert!(Pert::new(min, max).with_mode(mode).is_err()); } } #[test] - fn pert_distributions_can_be_compared() { - assert_eq!(Pert::new(1.0, 3.0, 2.0), Pert::new(1.0, 3.0, 2.0)); + fn distributions_can_be_compared() { + let (min, mode, max, shape) = (1.0, 2.0, 3.0, 4.0); + let p1 = Pert::new(min, max).with_mode(mode).unwrap(); + let mean = (min + shape * mode + max) / (shape + 2.0); + let p2 = Pert::new(min, max).with_mean(mean).unwrap(); + assert_eq!(p1, p2); + } + + #[test] + fn mode_almost_half_range() { + assert!(Pert::new(0.0f32, 0.48258883).with_mode(0.24129441).is_ok()); + } + + #[test] + fn almost_symmetric_about_zero() { + let distr = Pert::new(-10f32, 10f32).with_mode(f32::EPSILON); + assert!(distr.is_ok()); + } + + #[test] + fn almost_symmetric() { + let distr = Pert::new(0f32, 2f32).with_mode(1f32 + f32::EPSILON); + assert!(distr.is_ok()); } } diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index 31bfce52e3e..b142741e77c 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -250,7 +250,7 @@ fn pert_stability() { // mean = 4, var = 12/7 test_samples( 860, - Pert::new(2., 10., 3.).unwrap(), + Pert::new(2., 10.).with_mode(3.).unwrap(), &[ 4.908681667460367, 4.014196196158352, From 1e381d13ee060f678c9a4ca54124ab8af30b4402 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 16 Jul 2024 11:32:06 +0100 Subject: [PATCH 395/443] UniformFloat: allow inclusion of high in all cases (#1462) Fix #1299 by removing logic specific to ensuring that we emulate a closed range by excluding `high` from the result. --- CHANGELOG.md | 1 + src/distributions/uniform.rs | 165 ++++++++++++----------------------- src/distributions/utils.rs | 22 ----- 3 files changed, 59 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ce72a533f..39e3c5c7271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Move all benchmarks to new `benches` crate (#1439) - Annotate panicking methods with `#[track_caller]` (#1442, #1447) - Enable feature `small_rng` by default (#1455) +- Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 34a6b252f4d..5540b74e46e 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -51,7 +51,8 @@ //! Those methods should include an assertion to check the range is valid (i.e. //! `low < high`). The example below merely wraps another back-end. //! -//! The `new`, `new_inclusive` and `sample_single` functions use arguments of +//! The `new`, `new_inclusive`, `sample_single` and `sample_single_inclusive` +//! functions use arguments of //! type `SampleBorrow` to support passing in values by reference or //! by value. In the implementation of these functions, you can choose to //! simply use the reference returned by [`SampleBorrow::borrow`], or you can choose @@ -207,6 +208,11 @@ impl Uniform { /// Create a new `Uniform` instance, which samples uniformly from the half /// open range `[low, high)` (excluding `high`). /// + /// For discrete types (e.g. integers), samples will always be strictly less + /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), + /// samples may equal `high` due to loss of precision but may not be + /// greater than `high`. + /// /// Fails if `low >= high`, or if `low`, `high` or the range `high - low` is /// non-finite. In release mode, only the range is checked. pub fn new(low: B1, high: B2) -> Result, Error> @@ -265,6 +271,11 @@ pub trait UniformSampler: Sized { /// Construct self, with inclusive lower bound and exclusive upper bound `[low, high)`. /// + /// For discrete types (e.g. integers), samples will always be strictly less + /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), + /// samples may equal `high` due to loss of precision but may not be + /// greater than `high`. + /// /// Usually users should not call this directly but prefer to use /// [`Uniform::new`]. fn new(low: B1, high: B2) -> Result @@ -287,6 +298,11 @@ pub trait UniformSampler: Sized { /// Sample a single value uniformly from a range with inclusive lower bound /// and exclusive upper bound `[low, high)`. /// + /// For discrete types (e.g. integers), samples will always be strictly less + /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), + /// samples may equal `high` due to loss of precision but may not be + /// greater than `high`. + /// /// By default this is implemented using /// `UniformSampler::new(low, high).sample(rng)`. However, for some types /// more optimal implementations for single usage may be provided via this @@ -908,6 +924,33 @@ pub struct UniformFloat { macro_rules! uniform_float_impl { ($($meta:meta)?, $ty:ty, $uty:ident, $f_scalar:ident, $u_scalar:ident, $bits_to_discard:expr) => { + $(#[cfg($meta)])? + impl UniformFloat<$ty> { + /// Construct, reducing `scale` as required to ensure that rounding + /// can never yield values greater than `high`. + /// + /// Note: though it may be tempting to use a variant of this method + /// to ensure that samples from `[low, high)` are always strictly + /// less than `high`, this approach may be very slow where + /// `scale.abs()` is much smaller than `high.abs()` + /// (example: `low=0.99999999997819644, high=1.`). + fn new_bounded(low: $ty, high: $ty, mut scale: $ty) -> Self { + let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); + + loop { + let mask = (scale * max_rand + low).gt_mask(high); + if !mask.any() { + break; + } + scale = scale.decrease_masked(mask); + } + + debug_assert!(<$ty>::splat(0.0).all_le(scale)); + + UniformFloat { low, scale } + } + } + $(#[cfg($meta)])? impl SampleUniform for $ty { type Sampler = UniformFloat<$ty>; @@ -931,26 +974,13 @@ macro_rules! uniform_float_impl { if !(low.all_lt(high)) { return Err(Error::EmptyRange); } - let max_rand = <$ty>::splat( - ($u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, - ); - let mut scale = high - low; + let scale = high - low; if !(scale.all_finite()) { return Err(Error::NonFinite); } - loop { - let mask = (scale * max_rand + low).ge_mask(high); - if !mask.any() { - break; - } - scale = scale.decrease_masked(mask); - } - - debug_assert!(<$ty>::splat(0.0).all_le(scale)); - - Ok(UniformFloat { low, scale }) + Ok(Self::new_bounded(low, high, scale)) } fn new_inclusive(low_b: B1, high_b: B2) -> Result @@ -967,26 +997,14 @@ macro_rules! uniform_float_impl { if !low.all_le(high) { return Err(Error::EmptyRange); } - let max_rand = <$ty>::splat( - ($u_scalar::MAX >> $bits_to_discard).into_float_with_exponent(0) - 1.0, - ); - let mut scale = (high - low) / max_rand; + let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); + let scale = (high - low) / max_rand; if !scale.all_finite() { return Err(Error::NonFinite); } - loop { - let mask = (scale * max_rand + low).gt_mask(high); - if !mask.any() { - break; - } - scale = scale.decrease_masked(mask); - } - - debug_assert!(<$ty>::splat(0.0).all_le(scale)); - - Ok(UniformFloat { low, scale }) + Ok(Self::new_bounded(low, high, scale)) } fn sample(&self, rng: &mut R) -> Self::X { @@ -1010,72 +1028,7 @@ macro_rules! uniform_float_impl { B1: SampleBorrow + Sized, B2: SampleBorrow + Sized, { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - #[cfg(debug_assertions)] - if !low.all_finite() || !high.all_finite() { - return Err(Error::NonFinite); - } - if !low.all_lt(high) { - return Err(Error::EmptyRange); - } - let mut scale = high - low; - if !scale.all_finite() { - return Err(Error::NonFinite); - } - - loop { - // Generate a value in the range [1, 2) - let value1_2 = - (rng.random::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); - - // Get a value in the range [0, 1) to avoid overflow when multiplying by scale - let value0_1 = value1_2 - <$ty>::splat(1.0); - - // Doing multiply before addition allows some architectures - // to use a single instruction. - let res = value0_1 * scale + low; - - debug_assert!(low.all_le(res) || !scale.all_finite()); - if res.all_lt(high) { - return Ok(res); - } - - // This handles a number of edge cases. - // * `low` or `high` is NaN. In this case `scale` and - // `res` are going to end up as NaN. - // * `low` is negative infinity and `high` is finite. - // `scale` is going to be infinite and `res` will be - // NaN. - // * `high` is positive infinity and `low` is finite. - // `scale` is going to be infinite and `res` will - // be infinite or NaN (if value0_1 is 0). - // * `low` is negative infinity and `high` is positive - // infinity. `scale` will be infinite and `res` will - // be NaN. - // * `low` and `high` are finite, but `high - low` - // overflows to infinite. `scale` will be infinite - // and `res` will be infinite or NaN (if value0_1 is 0). - // So if `high` or `low` are non-finite, we are guaranteed - // to fail the `res < high` check above and end up here. - // - // While we technically should check for non-finite `low` - // and `high` before entering the loop, by doing the checks - // here instead, we allow the common case to avoid these - // checks. But we are still guaranteed that if `low` or - // `high` are non-finite we'll end up here and can do the - // appropriate checks. - // - // Likewise, `high - low` overflowing to infinity is also - // rare, so handle it here after the common case. - let mask = !scale.finite_mask(); - if mask.any() { - if !(low.all_finite() && high.all_finite()) { - return Err(Error::NonFinite); - } - scale = scale.decrease_masked(mask); - } - } + Self::sample_single_inclusive(low_b, high_b, rng) } #[inline] @@ -1465,14 +1418,14 @@ mod tests { let my_incl_uniform = Uniform::new_inclusive(low, high).unwrap(); for _ in 0..100 { let v = rng.sample(my_uniform).extract(lane); - assert!(low_scalar <= v && v < high_scalar); + assert!(low_scalar <= v && v <= high_scalar); let v = rng.sample(my_incl_uniform).extract(lane); assert!(low_scalar <= v && v <= high_scalar); let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng) .unwrap() .extract(lane); - assert!(low_scalar <= v && v < high_scalar); + assert!(low_scalar <= v && v <= high_scalar); let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive( low, high, &mut rng, ) @@ -1510,12 +1463,12 @@ mod tests { low_scalar ); - assert!(max_rng.sample(my_uniform).extract(lane) < high_scalar); + assert!(max_rng.sample(my_uniform).extract(lane) <= high_scalar); assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar); // sample_single cannot cope with max_rng: // assert!(<$ty as SampleUniform>::Sampler // ::sample_single(low, high, &mut max_rng).unwrap() - // .extract(lane) < high_scalar); + // .extract(lane) <= high_scalar); assert!( <$ty as SampleUniform>::Sampler::sample_single_inclusive( low, @@ -1543,7 +1496,7 @@ mod tests { ) .unwrap() .extract(lane) - < high_scalar + <= high_scalar ); } } @@ -1590,10 +1543,9 @@ mod tests { #[cfg(all(feature = "std", panic = "unwind"))] fn test_float_assertions() { use super::SampleUniform; - use std::panic::catch_unwind; - fn range(low: T, high: T) { + fn range(low: T, high: T) -> Result { let mut rng = crate::test::rng(253); - T::Sampler::sample_single(low, high, &mut rng).unwrap(); + T::Sampler::sample_single(low, high, &mut rng) } macro_rules! t { @@ -1616,10 +1568,9 @@ mod tests { for lane in 0..<$ty>::LEN { let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); - assert!(catch_unwind(|| range(low, high)).is_err()); + assert!(range(low, high).is_err()); assert!(Uniform::new(low, high).is_err()); assert!(Uniform::new_inclusive(low, high).is_err()); - assert!(catch_unwind(|| range(low, low)).is_err()); assert!(Uniform::new(low, low).is_err()); } } diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index aee92b67902..7e84665ec42 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -218,9 +218,7 @@ pub(crate) trait FloatSIMDUtils { fn all_finite(self) -> bool; type Mask; - fn finite_mask(self) -> Self::Mask; fn gt_mask(self, other: Self) -> Self::Mask; - fn ge_mask(self, other: Self) -> Self::Mask; // Decrease all lanes where the mask is `true` to the next lower value // representable by the floating-point type. At least one of the lanes @@ -292,21 +290,11 @@ macro_rules! scalar_float_impl { self.is_finite() } - #[inline(always)] - fn finite_mask(self) -> Self::Mask { - self.is_finite() - } - #[inline(always)] fn gt_mask(self, other: Self) -> Self::Mask { self > other } - #[inline(always)] - fn ge_mask(self, other: Self) -> Self::Mask { - self >= other - } - #[inline(always)] fn decrease_masked(self, mask: Self::Mask) -> Self { debug_assert!(mask, "At least one lane must be set"); @@ -368,21 +356,11 @@ macro_rules! simd_impl { self.is_finite().all() } - #[inline(always)] - fn finite_mask(self) -> Self::Mask { - self.is_finite() - } - #[inline(always)] fn gt_mask(self, other: Self) -> Self::Mask { self.simd_gt(other) } - #[inline(always)] - fn ge_mask(self, other: Self) -> Self::Mask { - self.simd_ge(other) - } - #[inline(always)] fn decrease_masked(self, mask: Self::Mask) -> Self { // Casting a mask into ints will produce all bits set for From e4874a6dd73f276978cb1bdd3302aee19f600081 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 20 Jul 2024 15:42:52 +0700 Subject: [PATCH 396/443] Fix some typos. (#1472) --- rand_core/src/le.rs | 4 ++-- rand_core/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rand_core/src/le.rs b/rand_core/src/le.rs index 92ba7d755c4..cee84c2f327 100644 --- a/rand_core/src/le.rs +++ b/rand_core/src/le.rs @@ -15,7 +15,7 @@ /// /// # Panics /// -/// If `dst` has insufficent space (`4*dst.len() < src.len()`). +/// If `dst` has insufficient space (`4*dst.len() < src.len()`). #[inline] #[track_caller] pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { @@ -29,7 +29,7 @@ pub fn read_u32_into(src: &[u8], dst: &mut [u32]) { /// /// # Panics /// -/// If `dst` has insufficent space (`8*dst.len() < src.len()`). +/// If `dst` has insufficient space (`8*dst.len() < src.len()`). #[inline] #[track_caller] pub fn read_u64_into(src: &[u8], dst: &mut [u64]) { diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 78b7d655458..275cedc7418 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -419,7 +419,7 @@ pub trait SeedableRng: Sized { /// Create a new PRNG seeded from a potentially fallible `Rng`. /// - /// See [`from_rng`][SeedableRng::from_rng] docs for more infromation. + /// See [`from_rng`][SeedableRng::from_rng] docs for more information. fn try_from_rng(mut rng: R) -> Result { let mut seed = Self::Seed::default(); rng.try_fill_bytes(seed.as_mut())?; From f3aab23fba4394f2176ba5287930e21c77743b86 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 20 Jul 2024 15:56:27 +0700 Subject: [PATCH 397/443] Fix `clippy::doc_markdown` warnings. (#1474) --- clippy.toml | 2 ++ rand_core/src/block.rs | 2 +- src/distributions/other.rs | 2 +- src/distributions/uniform.rs | 4 ++-- src/seq/index.rs | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 clippy.toml diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000000..14793c52048 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,2 @@ +# Don't warn about these identifiers when using clippy::doc_markdown. +doc-valid-idents = ["ChaCha", "ChaCha12", "SplitMix64", "ZiB", ".."] diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 6872af432f5..9f227bb75b4 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -276,7 +276,7 @@ impl> CryptoRng for BlockRng {} /// then the other half is then consumed, however both [`next_u64`] and /// [`fill_bytes`] discard the rest of any half-consumed `u64`s when called. /// -/// [`fill_bytes`] `] consume a whole number of `u64` values. If the requested length +/// [`fill_bytes`] consumes a whole number of `u64` values. If the requested length /// is not a multiple of 8, some bytes will be discarded. /// /// [`next_u32`]: RngCore::next_u32 diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 21289ca94d0..5b05854ac24 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -189,7 +189,7 @@ where } } -/// Implement `Distribution<(A, B, C, ...)> for Standard, using the list of +/// Implement `Distribution<(A, B, C, ...)> for Standard`, using the list of /// identifiers macro_rules! tuple_impl { ($($tyvar:ident)*) => { diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 5540b74e46e..306b0cced65 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -371,8 +371,8 @@ impl TryFrom> for Uniform { } /// Helper trait similar to [`Borrow`] but implemented -/// only for SampleUniform and references to SampleUniform in -/// order to resolve ambiguity issues. +/// only for [`SampleUniform`] and references to [`SampleUniform`] +/// in order to resolve ambiguity issues. /// /// [`Borrow`]: std::borrow::Borrow pub trait SampleBorrow { diff --git a/src/seq/index.rs b/src/seq/index.rs index 471e87c1e2c..709d62c86b1 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -339,7 +339,7 @@ where /// which will be called once for each index. /// /// This implementation uses the algorithm described by Efraimidis and Spirakis -/// in this paper: https://doi.org/10.1016/j.ipl.2005.11.003 +/// in this paper: /// It uses `O(length + amount)` space and `O(length)` time. /// /// Error cases: From 605476c4e4eda450b20a801080a34cf05c9dae58 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 23 Jul 2024 13:50:31 +0100 Subject: [PATCH 398/443] Portability fixes (#1469) - Fix portability of `choose_multiple_array` - Fix portability of `rand::distributions::Slice` --- CHANGELOG.md | 3 ++- rand_distr/src/dirichlet.rs | 9 +------ src/distributions/slice.rs | 48 +++++++++++++++++++++++++++++++++++-- src/distributions/utils.rs | 2 ++ src/rngs/mod.rs | 5 +++- src/rngs/small.rs | 4 ++-- src/seq/index.rs | 3 ++- src/seq/slice.rs | 11 ++++++--- 8 files changed, 67 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e3c5c7271..9667aebfa13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,14 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [Unreleased] - Add `rand::distributions::WeightedIndex::{weight, weights, total_weight}` (#1420) -- Add `IndexedRandom::choose_multiple_array`, `index::sample_array` (#1453) +- Add `IndexedRandom::choose_multiple_array`, `index::sample_array` (#1453, #1469) - Bump the MSRV to 1.61.0 - Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) - Move all benchmarks to new `benches` crate (#1439) - Annotate panicking methods with `#[track_caller]` (#1442, #1447) - Enable feature `small_rng` by default (#1455) - Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) +- Fix portability of `rand::distributions::Slice` (#1469) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index aae1e0750ff..3cd4c09eea7 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -333,20 +333,13 @@ where #[cfg(test)] mod test { use super::*; - use alloc::vec::Vec; #[test] fn test_dirichlet() { let d = Dirichlet::new([1.0, 2.0, 3.0]).unwrap(); let mut rng = crate::test::rng(221); let samples = d.sample(&mut rng); - let _: Vec = samples - .into_iter() - .map(|x| { - assert!(x > 0.0); - x - }) - .collect(); + assert!(samples.into_iter().all(|x: f64| x > 0.0)); } #[test] diff --git a/src/distributions/slice.rs b/src/distributions/slice.rs index 10f830b7bb3..8b8f9662595 100644 --- a/src/distributions/slice.rs +++ b/src/distributions/slice.rs @@ -9,9 +9,39 @@ use core::num::NonZeroUsize; use crate::distributions::{Distribution, Uniform}; +use crate::Rng; #[cfg(feature = "alloc")] use alloc::string::String; +#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] +compile_error!("unsupported pointer width"); + +#[derive(Debug, Clone, Copy)] +enum UniformUsize { + U32(Uniform), + #[cfg(target_pointer_width = "64")] + U64(Uniform), +} + +impl UniformUsize { + pub fn new(ubound: usize) -> Result { + #[cfg(target_pointer_width = "64")] + if ubound > (u32::MAX as usize) { + return Uniform::new(0, ubound as u64).map(UniformUsize::U64); + } + + Uniform::new(0, ubound as u32).map(UniformUsize::U32) + } + + pub fn sample(&self, rng: &mut R) -> usize { + match self { + UniformUsize::U32(uu) => uu.sample(rng) as usize, + #[cfg(target_pointer_width = "64")] + UniformUsize::U64(uu) => uu.sample(rng) as usize, + } + } +} + /// A distribution to sample items uniformly from a slice. /// /// [`Slice::new`] constructs a distribution referencing a slice and uniformly @@ -68,7 +98,7 @@ use alloc::string::String; #[derive(Debug, Clone, Copy)] pub struct Slice<'a, T> { slice: &'a [T], - range: Uniform, + range: UniformUsize, num_choices: NonZeroUsize, } @@ -80,7 +110,7 @@ impl<'a, T> Slice<'a, T> { Ok(Self { slice, - range: Uniform::new(0, num_choices.get()).unwrap(), + range: UniformUsize::new(num_choices.get()).unwrap(), num_choices, }) } @@ -161,3 +191,17 @@ impl<'a> super::DistString for Slice<'a, char> { } } } + +#[cfg(test)] +mod test { + use super::*; + use core::iter; + + #[test] + fn value_stability() { + let rng = crate::test::rng(651); + let slice = Slice::new(b"escaped emus explore extensively").unwrap(); + let expected = b"eaxee"; + assert!(iter::zip(slice.sample_iter(rng), expected).all(|(a, b)| a == b)); + } +} diff --git a/src/distributions/utils.rs b/src/distributions/utils.rs index 7e84665ec42..b54dc6d6c4e 100644 --- a/src/distributions/utils.rs +++ b/src/distributions/utils.rs @@ -241,7 +241,9 @@ pub(crate) trait FloatSIMDScalarUtils: FloatSIMDUtils { /// Implement functions on f32/f64 to give them APIs similar to SIMD types pub(crate) trait FloatAsSIMD: Sized { + #[cfg(test)] const LEN: usize = 1; + #[inline(always)] fn splat(scalar: Self) -> Self { scalar diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index afd9246d4ab..1aa65149a14 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -86,7 +86,10 @@ pub mod mock; // Public so we don't export `StepRng` directly, making it a bit #[cfg(feature = "small_rng")] mod small; -#[cfg(all(feature = "small_rng", not(target_pointer_width = "64")))] +#[cfg(all( + feature = "small_rng", + any(target_pointer_width = "32", target_pointer_width = "16") +))] mod xoshiro128plusplus; #[cfg(all(feature = "small_rng", target_pointer_width = "64"))] mod xoshiro256plusplus; diff --git a/src/rngs/small.rs b/src/rngs/small.rs index 835eadc0bca..ea7df062842 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -10,10 +10,10 @@ use rand_core::{RngCore, SeedableRng}; +#[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] +type Rng = super::xoshiro128plusplus::Xoshiro128PlusPlus; #[cfg(target_pointer_width = "64")] type Rng = super::xoshiro256plusplus::Xoshiro256PlusPlus; -#[cfg(not(target_pointer_width = "64"))] -type Rng = super::xoshiro128plusplus::Xoshiro128PlusPlus; /// A small-state, fast, non-crypto, non-portable PRNG /// diff --git a/src/seq/index.rs b/src/seq/index.rs index 709d62c86b1..e37198bd67a 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -7,6 +7,7 @@ // except according to those terms. //! Low-level API for sampling indices +use super::gen_index; #[cfg(feature = "alloc")] use alloc::vec::{self, Vec}; use core::slice; @@ -288,7 +289,7 @@ where // Floyd's algorithm let mut indices = [0; N]; for (i, j) in (len - N..len).enumerate() { - let t = rng.gen_range(0..=j); + let t = gen_index(rng, j + 1); if let Some(pos) = indices[0..i].iter().position(|&x| x == t) { indices[pos] = j; } diff --git a/src/seq/slice.rs b/src/seq/slice.rs index 60a0b1e7e40..c82998fd358 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -495,19 +495,24 @@ mod test { assert_eq!(chars.choose(&mut r), Some(&'l')); assert_eq!(nums.choose_mut(&mut r), Some(&mut 3)); + assert_eq!( + &chars.choose_multiple_array(&mut r), + &Some(['f', 'i', 'd', 'b', 'c', 'm', 'j', 'k']) + ); + #[cfg(feature = "alloc")] assert_eq!( &chars .choose_multiple(&mut r, 8) .cloned() .collect::>(), - &['f', 'i', 'd', 'b', 'c', 'm', 'j', 'k'] + &['h', 'm', 'd', 'b', 'c', 'e', 'n', 'f'] ); #[cfg(feature = "alloc")] - assert_eq!(chars.choose_weighted(&mut r, |_| 1), Ok(&'l')); + assert_eq!(chars.choose_weighted(&mut r, |_| 1), Ok(&'i')); #[cfg(feature = "alloc")] - assert_eq!(nums.choose_weighted_mut(&mut r, |_| 1), Ok(&mut 8)); + assert_eq!(nums.choose_weighted_mut(&mut r, |_| 1), Ok(&mut 2)); let mut r = crate::test::rng(414); nums.shuffle(&mut r); From 2d5948d264229fce69aaa979ee6f035dff9073b6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 23 Jul 2024 14:14:11 +0100 Subject: [PATCH 399/443] rand::distributions -> distr; split uniform module (#1470) --- CHANGELOG.md | 1 + README.md | 2 +- benches/Cargo.toml | 4 +- benches/benches/base_distributions.rs | 6 +- benches/benches/misc.rs | 2 +- benches/benches/weighted.rs | 2 +- benches/src/{distributions.rs => distr.rs} | 0 benches/src/uniform.rs | 2 +- benches/src/uniform_float.rs | 2 +- examples/monte-carlo.rs | 2 +- examples/monty-hall.rs | 2 +- examples/rayon-monte-carlo.rs | 2 +- rand_distr/README.md | 4 +- rand_distr/src/hypergeometric.rs | 2 +- rand_distr/src/lib.rs | 8 +- rand_distr/src/utils.rs | 2 +- rand_distr/src/weighted_tree.rs | 6 +- rand_distr/src/zeta.rs | 2 +- src/{distributions => distr}/bernoulli.rs | 6 +- src/{distributions => distr}/distribution.rs | 10 +- src/{distributions => distr}/float.rs | 20 +- src/{distributions => distr}/integer.rs | 2 +- src/{distributions => distr}/mod.rs | 4 +- src/{distributions => distr}/other.rs | 8 +- src/{distributions => distr}/slice.rs | 9 +- src/distr/uniform.rs | 581 ++++++ src/distr/uniform_float.rs | 450 +++++ src/distr/uniform_int.rs | 521 +++++ src/distr/uniform_other.rs | 322 +++ src/{distributions => distr}/utils.rs | 0 .../weighted_index.rs | 14 +- src/distributions/uniform.rs | 1774 ----------------- src/lib.rs | 8 +- src/prelude.rs | 2 +- src/rng.rs | 30 +- src/rngs/mock.rs | 4 +- src/seq/index.rs | 51 +- src/seq/mod.rs | 45 +- src/seq/slice.rs | 16 +- utils/ziggurat_tables.py | 2 +- 40 files changed, 2012 insertions(+), 1918 deletions(-) rename benches/src/{distributions.rs => distr.rs} (100%) rename src/{distributions => distr}/bernoulli.rs (98%) rename src/{distributions => distr}/distribution.rs (96%) rename src/{distributions => distr}/float.rs (96%) rename src/{distributions => distr}/integer.rs (99%) rename src/{distributions => distr}/mod.rs (99%) rename src/{distributions => distr}/other.rs (98%) rename src/{distributions => distr}/slice.rs (97%) create mode 100644 src/distr/uniform.rs create mode 100644 src/distr/uniform_float.rs create mode 100644 src/distr/uniform_int.rs create mode 100644 src/distr/uniform_other.rs rename src/{distributions => distr}/utils.rs (100%) rename src/{distributions => distr}/weighted_index.rs (98%) delete mode 100644 src/distributions/uniform.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 9667aebfa13..1a098453c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Enable feature `small_rng` by default (#1455) - Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) - Fix portability of `rand::distributions::Slice` (#1469) +- Rename `rand::distributions` to `rand::distr` (#1470) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/README.md b/README.md index 9fa7a2f8528..18f22a89eb8 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ A Rust library for random number generation, featuring: ([see the book](https://rust-random.github.io/book/crates.html)) - Fast implementations of the best-in-class [cryptographic](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and [non-cryptographic](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators -- A flexible [`distributions`](https://docs.rs/rand/*/rand/distributions/index.html) module +- A flexible [`distributions`](https://docs.rs/rand/*/rand/distr/index.html) module - Samplers for a large number of random number distributions via our own [`rand_distr`](https://docs.rs/rand_distr) and via the [`statrs`](https://docs.rs/statrs/0.13.0/statrs/) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index b3068c2f758..083512d0bc6 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -15,8 +15,8 @@ criterion = "0.5" criterion-cycles-per-byte = "0.6" [[bench]] -name = "distributions" -path = "src/distributions.rs" +name = "distr" +path = "src/distr.rs" harness = false [[bench]] diff --git a/benches/benches/base_distributions.rs b/benches/benches/base_distributions.rs index c60ce47aeab..17202a30350 100644 --- a/benches/benches/base_distributions.rs +++ b/benches/benches/base_distributions.rs @@ -16,8 +16,8 @@ extern crate test; const RAND_BENCH_N: u64 = 1000; -use rand::distributions::{Alphanumeric, Open01, OpenClosed01, Standard, Uniform}; -use rand::distributions::uniform::{UniformInt, UniformSampler}; +use rand::distr::{Alphanumeric, Open01, OpenClosed01, Standard, Uniform}; +use rand::distr::uniform::{UniformInt, UniformSampler}; use core::mem::size_of; use core::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8}; use core::time::Duration; @@ -253,7 +253,7 @@ gen_range_float!(gen_range_f32, f32, -20000.0f32, 100000.0); gen_range_float!(gen_range_f64, f64, 123.456f64, 7890.12); -// In src/distributions/uniform.rs, we say: +// In src/distr/uniform.rs, we say: // Implementation of [`uniform_single`] is optional, and is only useful when // the implementation can be faster than `Self::new(low, high).sample(rng)`. diff --git a/benches/benches/misc.rs b/benches/benches/misc.rs index 50dfc0ea8a7..8a3b4767ef2 100644 --- a/benches/benches/misc.rs +++ b/benches/benches/misc.rs @@ -14,7 +14,7 @@ const RAND_BENCH_N: u64 = 1000; use test::Bencher; -use rand::distributions::{Bernoulli, Distribution, Standard}; +use rand::distr::{Bernoulli, Distribution, Standard}; use rand::prelude::*; use rand_pcg::{Pcg32, Pcg64Mcg}; diff --git a/benches/benches/weighted.rs b/benches/benches/weighted.rs index 68722908a9e..da437ab5b0b 100644 --- a/benches/benches/weighted.rs +++ b/benches/benches/weighted.rs @@ -10,7 +10,7 @@ extern crate test; -use rand::distributions::WeightedIndex; +use rand::distr::WeightedIndex; use rand::Rng; use test::Bencher; diff --git a/benches/src/distributions.rs b/benches/src/distr.rs similarity index 100% rename from benches/src/distributions.rs rename to benches/src/distr.rs diff --git a/benches/src/uniform.rs b/benches/src/uniform.rs index 948d1315889..78eb066eac7 100644 --- a/benches/src/uniform.rs +++ b/benches/src/uniform.rs @@ -10,7 +10,7 @@ use core::time::Duration; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use rand::distributions::uniform::{SampleRange, Uniform}; +use rand::distr::uniform::{SampleRange, Uniform}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use rand_pcg::{Pcg32, Pcg64}; diff --git a/benches/src/uniform_float.rs b/benches/src/uniform_float.rs index 91c0eff4b7b..f33a2b729d3 100644 --- a/benches/src/uniform_float.rs +++ b/benches/src/uniform_float.rs @@ -14,7 +14,7 @@ use core::time::Duration; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; -use rand::distributions::uniform::{SampleUniform, Uniform, UniformSampler}; +use rand::distr::uniform::{SampleUniform, Uniform, UniformSampler}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use rand_pcg::{Pcg32, Pcg64}; diff --git a/examples/monte-carlo.rs b/examples/monte-carlo.rs index 21ab109ce05..0f50a4aeb0d 100644 --- a/examples/monte-carlo.rs +++ b/examples/monte-carlo.rs @@ -23,7 +23,7 @@ //! We can use the above fact to estimate the value of π: pick many points in //! the square at random, calculate the fraction that fall within the circle, //! and multiply this fraction by 4. -use rand::distributions::{Distribution, Uniform}; +use rand::distr::{Distribution, Uniform}; fn main() { let range = Uniform::new(-1.0f64, 1.0).unwrap(); diff --git a/examples/monty-hall.rs b/examples/monty-hall.rs index aaff41cad33..a6fcd04e802 100644 --- a/examples/monty-hall.rs +++ b/examples/monty-hall.rs @@ -26,7 +26,7 @@ //! //! [Monty Hall Problem]: https://en.wikipedia.org/wiki/Monty_Hall_problem -use rand::distributions::{Distribution, Uniform}; +use rand::distr::{Distribution, Uniform}; use rand::Rng; struct SimulationResult { diff --git a/examples/rayon-monte-carlo.rs b/examples/rayon-monte-carlo.rs index 839a1593a1b..31d8e681067 100644 --- a/examples/rayon-monte-carlo.rs +++ b/examples/rayon-monte-carlo.rs @@ -38,7 +38,7 @@ //! over BATCH_SIZE trials. Manually batching also turns out to be faster //! for the nondeterministic version of this program as well. -use rand::distributions::{Distribution, Uniform}; +use rand::distr::{Distribution, Uniform}; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; use rayon::prelude::*; diff --git a/rand_distr/README.md b/rand_distr/README.md index 16a44bc85c9..0c8b20b95ef 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -8,7 +8,7 @@ Implements a full suite of random number distribution sampling routines. -This crate is a superset of the [rand::distributions] module, including support +This crate is a superset of the [rand::distr] module, including support for sampling from Beta, Binomial, Cauchy, ChiSquared, Dirichlet, Exponential, FisherF, Gamma, Geometric, Hypergeometric, InverseGaussian, LogNormal, Normal, Pareto, PERT, Poisson, StudentT, Triangular and Weibull distributions. Sampling @@ -46,7 +46,7 @@ can be enabled. (Note that any other crate depending on `num-traits` with the [statrs]: https://github.com/boxtown/statrs -[rand::distributions]: https://rust-random.github.io/rand/rand/distributions/index.html +[rand::distr]: https://rust-random.github.io/rand/rand/distr/index.html ## License diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 4e4f4306353..683db15314d 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -4,7 +4,7 @@ use crate::Distribution; use core::fmt; #[allow(unused_imports)] use num_traits::Float; -use rand::distributions::uniform::Uniform; +use rand::distr::uniform::Uniform; use rand::Rng; #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index f6f3ad54cfa..90a534ff8cb 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -27,8 +27,8 @@ //! //! ## Re-exports //! -//! This crate is a super-set of the [`rand::distributions`] module. See the -//! [`rand::distributions`] module documentation for an overview of the core +//! This crate is a super-set of the [`rand::distr`] module. See the +//! [`rand::distr`] module documentation for an overview of the core //! [`Distribution`] trait and implementations. //! //! The following are re-exported: @@ -93,7 +93,7 @@ extern crate std; #[allow(unused)] use rand::Rng; -pub use rand::distributions::{ +pub use rand::distr::{ uniform, Alphanumeric, Bernoulli, BernoulliError, DistIter, Distribution, Open01, OpenClosed01, Standard, Uniform, }; @@ -129,7 +129,7 @@ pub use self::weibull::{Error as WeibullError, Weibull}; pub use self::zeta::{Error as ZetaError, Zeta}; pub use self::zipf::{Error as ZipfError, Zipf}; #[cfg(feature = "alloc")] -pub use rand::distributions::{WeightError, WeightedIndex}; +pub use rand::distr::{WeightError, WeightedIndex}; pub use student_t::StudentT; #[cfg(feature = "alloc")] pub use weighted_alias::WeightedAliasIndex; diff --git a/rand_distr/src/utils.rs b/rand_distr/src/utils.rs index fb49ab85762..5879a152670 100644 --- a/rand_distr/src/utils.rs +++ b/rand_distr/src/utils.rs @@ -10,7 +10,7 @@ use crate::ziggurat_tables; use num_traits::Float; -use rand::distributions::hidden_export::IntoFloat; +use rand::distr::hidden_export::IntoFloat; use rand::Rng; /// Calculates ln(gamma(x)) (natural logarithm of the gamma diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index f3463bcd960..28edab700d4 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -14,8 +14,8 @@ use core::ops::SubAssign; use super::WeightError; use crate::Distribution; use alloc::vec::Vec; -use rand::distributions::uniform::{SampleBorrow, SampleUniform}; -use rand::distributions::Weight; +use rand::distr::uniform::{SampleBorrow, SampleUniform}; +use rand::distr::Weight; use rand::Rng; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; @@ -30,7 +30,7 @@ use serde::{Deserialize, Serialize}; /// /// # Key differences /// -/// The main distinction between [`WeightedTreeIndex`] and [`rand::distributions::WeightedIndex`] +/// The main distinction between [`WeightedTreeIndex`] and [`rand::distr::WeightedIndex`] /// lies in the internal representation of weights. In [`WeightedTreeIndex`], /// weights are structured as a tree, which is optimized for frequent updates of the weights. /// diff --git a/rand_distr/src/zeta.rs b/rand_distr/src/zeta.rs index da146883f0a..922458436f5 100644 --- a/rand_distr/src/zeta.rs +++ b/rand_distr/src/zeta.rs @@ -11,7 +11,7 @@ use crate::{Distribution, Standard}; use core::fmt; use num_traits::Float; -use rand::{distributions::OpenClosed01, Rng}; +use rand::{distr::OpenClosed01, Rng}; /// The [Zeta distribution](https://en.wikipedia.org/wiki/Zeta_distribution) `Zeta(s)`. /// diff --git a/src/distributions/bernoulli.rs b/src/distr/bernoulli.rs similarity index 98% rename from src/distributions/bernoulli.rs rename to src/distr/bernoulli.rs index e49b415fea9..5a56d079a83 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distr/bernoulli.rs @@ -8,7 +8,7 @@ //! The Bernoulli distribution `Bernoulli(p)`. -use crate::distributions::Distribution; +use crate::distr::Distribution; use crate::Rng; use core::fmt; @@ -31,7 +31,7 @@ use serde::{Deserialize, Serialize}; /// # Example /// /// ```rust -/// use rand::distributions::{Bernoulli, Distribution}; +/// use rand::distr::{Bernoulli, Distribution}; /// /// let d = Bernoulli::new(0.3).unwrap(); /// let v = d.sample(&mut rand::thread_rng()); @@ -153,7 +153,7 @@ impl Distribution for Bernoulli { #[cfg(test)] mod test { use super::Bernoulli; - use crate::distributions::Distribution; + use crate::distr::Distribution; use crate::Rng; #[test] diff --git a/src/distributions/distribution.rs b/src/distr/distribution.rs similarity index 96% rename from src/distributions/distribution.rs rename to src/distr/distribution.rs index d545eeea457..8a59e11e13c 100644 --- a/src/distributions/distribution.rs +++ b/src/distr/distribution.rs @@ -49,7 +49,7 @@ pub trait Distribution { /// /// ``` /// use rand::thread_rng; - /// use rand::distributions::{Distribution, Alphanumeric, Uniform, Standard}; + /// use rand::distr::{Distribution, Alphanumeric, Uniform, Standard}; /// /// let mut rng = thread_rng(); /// @@ -89,7 +89,7 @@ pub trait Distribution { /// /// ``` /// use rand::thread_rng; - /// use rand::distributions::{Distribution, Uniform}; + /// use rand::distr::{Distribution, Uniform}; /// /// let mut rng = thread_rng(); /// @@ -201,12 +201,12 @@ pub trait DistString { #[cfg(test)] mod tests { - use crate::distributions::{Distribution, Uniform}; + use crate::distr::{Distribution, Uniform}; use crate::Rng; #[test] fn test_distributions_iter() { - use crate::distributions::Open01; + use crate::distr::Open01; let mut rng = crate::test::rng(210); let distr = Open01; let mut iter = Distribution::::sample_iter(distr, &mut rng); @@ -248,7 +248,7 @@ mod tests { #[test] #[cfg(feature = "alloc")] fn test_dist_string() { - use crate::distributions::{Alphanumeric, DistString, Standard}; + use crate::distr::{Alphanumeric, DistString, Standard}; use core::str; let mut rng = crate::test::rng(213); diff --git a/src/distributions/float.rs b/src/distr/float.rs similarity index 96% rename from src/distributions/float.rs rename to src/distr/float.rs index 427385e50a2..67e6d4b250d 100644 --- a/src/distributions/float.rs +++ b/src/distr/float.rs @@ -8,8 +8,8 @@ //! Basic floating-point number distributions -use crate::distributions::utils::{FloatAsSIMD, FloatSIMDUtils, IntAsSIMD}; -use crate::distributions::{Distribution, Standard}; +use crate::distr::utils::{FloatAsSIMD, FloatSIMDUtils, IntAsSIMD}; +use crate::distr::{Distribution, Standard}; use crate::Rng; use core::mem; #[cfg(feature = "simd_support")] @@ -33,15 +33,15 @@ use serde::{Deserialize, Serialize}; /// # Example /// ``` /// use rand::{thread_rng, Rng}; -/// use rand::distributions::OpenClosed01; +/// use rand::distr::OpenClosed01; /// /// let val: f32 = thread_rng().sample(OpenClosed01); /// println!("f32 from (0, 1): {}", val); /// ``` /// -/// [`Standard`]: crate::distributions::Standard -/// [`Open01`]: crate::distributions::Open01 -/// [`Uniform`]: crate::distributions::uniform::Uniform +/// [`Standard`]: crate::distr::Standard +/// [`Open01`]: crate::distr::Open01 +/// [`Uniform`]: crate::distr::uniform::Uniform #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct OpenClosed01; @@ -60,15 +60,15 @@ pub struct OpenClosed01; /// # Example /// ``` /// use rand::{thread_rng, Rng}; -/// use rand::distributions::Open01; +/// use rand::distr::Open01; /// /// let val: f32 = thread_rng().sample(Open01); /// println!("f32 from (0, 1): {}", val); /// ``` /// -/// [`Standard`]: crate::distributions::Standard -/// [`OpenClosed01`]: crate::distributions::OpenClosed01 -/// [`Uniform`]: crate::distributions::uniform::Uniform +/// [`Standard`]: crate::distr::Standard +/// [`OpenClosed01`]: crate::distr::OpenClosed01 +/// [`Uniform`]: crate::distr::uniform::Uniform #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct Open01; diff --git a/src/distributions/integer.rs b/src/distr/integer.rs similarity index 99% rename from src/distributions/integer.rs rename to src/distr/integer.rs index 66258dcbd5c..49546a39417 100644 --- a/src/distributions/integer.rs +++ b/src/distr/integer.rs @@ -8,7 +8,7 @@ //! The implementations of the `Standard` distribution for integer types. -use crate::distributions::{Distribution, Standard}; +use crate::distr::{Distribution, Standard}; use crate::Rng; #[cfg(all(target_arch = "x86", feature = "simd_support"))] use core::arch::x86::__m512i; diff --git a/src/distributions/mod.rs b/src/distr/mod.rs similarity index 99% rename from src/distributions/mod.rs rename to src/distr/mod.rs index b31ebe14f18..8b5c0054271 100644 --- a/src/distributions/mod.rs +++ b/src/distr/mod.rs @@ -172,7 +172,7 @@ use crate::Rng; /// ``` /// # #![allow(dead_code)] /// use rand::Rng; -/// use rand::distributions::{Distribution, Standard}; +/// use rand::distr::{Distribution, Standard}; /// /// struct MyF32 { /// x: f32, @@ -188,7 +188,7 @@ use crate::Rng; /// ## Example usage /// ``` /// use rand::prelude::*; -/// use rand::distributions::Standard; +/// use rand::distr::Standard; /// /// let val: f32 = StdRng::from_os_rng().sample(Standard); /// println!("f32 from [0, 1): {}", val); diff --git a/src/distributions/other.rs b/src/distr/other.rs similarity index 98% rename from src/distributions/other.rs rename to src/distr/other.rs index 5b05854ac24..3ce24e00a01 100644 --- a/src/distributions/other.rs +++ b/src/distr/other.rs @@ -14,8 +14,8 @@ use core::char; use core::num::Wrapping; #[cfg(feature = "alloc")] -use crate::distributions::DistString; -use crate::distributions::{Distribution, Standard, Uniform}; +use crate::distr::DistString; +use crate::distr::{Distribution, Standard, Uniform}; use crate::Rng; use core::mem::{self, MaybeUninit}; @@ -35,7 +35,7 @@ use serde::{Deserialize, Serialize}; /// /// ``` /// use rand::{Rng, thread_rng}; -/// use rand::distributions::Alphanumeric; +/// use rand::distr::Alphanumeric; /// /// let mut rng = thread_rng(); /// let chars: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect(); @@ -45,7 +45,7 @@ use serde::{Deserialize, Serialize}; /// The [`DistString`] trait provides an easier method of generating /// a random `String`, and offers more efficient allocation: /// ``` -/// use rand::distributions::{Alphanumeric, DistString}; +/// use rand::distr::{Alphanumeric, DistString}; /// let string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); /// println!("Random string: {}", string); /// ``` diff --git a/src/distributions/slice.rs b/src/distr/slice.rs similarity index 97% rename from src/distributions/slice.rs rename to src/distr/slice.rs index 8b8f9662595..4677b4e89e4 100644 --- a/src/distributions/slice.rs +++ b/src/distr/slice.rs @@ -8,7 +8,7 @@ use core::num::NonZeroUsize; -use crate::distributions::{Distribution, Uniform}; +use crate::distr::{Distribution, Uniform}; use crate::Rng; #[cfg(feature = "alloc")] use alloc::string::String; @@ -63,7 +63,7 @@ impl UniformUsize { /// /// ``` /// use rand::Rng; -/// use rand::distributions::Slice; +/// use rand::distr::Slice; /// /// let vowels = ['a', 'e', 'i', 'o', 'u']; /// let vowels_dist = Slice::new(&vowels).unwrap(); @@ -146,10 +146,7 @@ pub struct EmptySlice; impl core::fmt::Display for EmptySlice { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "Tried to create a `distributions::Slice` with an empty slice" - ) + write!(f, "Tried to create a `distr::Slice` with an empty slice") } } diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs new file mode 100644 index 00000000000..fa245c3aaf8 --- /dev/null +++ b/src/distr/uniform.rs @@ -0,0 +1,581 @@ +// Copyright 2018-2020 Developers of the Rand project. +// Copyright 2017 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A distribution uniformly sampling numbers within a given range. +//! +//! [`Uniform`] is the standard distribution to sample uniformly from a range; +//! e.g. `Uniform::new_inclusive(1, 6).unwrap()` can sample integers from 1 to 6, like a +//! standard die. [`Rng::gen_range`] supports any type supported by [`Uniform`]. +//! +//! This distribution is provided with support for several primitive types +//! (all integer and floating-point types) as well as [`std::time::Duration`], +//! and supports extension to user-defined types via a type-specific *back-end* +//! implementation. +//! +//! The types [`UniformInt`], [`UniformFloat`] and [`UniformDuration`] are the +//! back-ends supporting sampling from primitive integer and floating-point +//! ranges as well as from [`std::time::Duration`]; these types do not normally +//! need to be used directly (unless implementing a derived back-end). +//! +//! # Example usage +//! +//! ``` +//! use rand::{Rng, thread_rng}; +//! use rand::distr::Uniform; +//! +//! let mut rng = thread_rng(); +//! let side = Uniform::new(-10.0, 10.0).unwrap(); +//! +//! // sample between 1 and 10 points +//! for _ in 0..rng.gen_range(1..=10) { +//! // sample a point from the square with sides -10 - 10 in two dimensions +//! let (x, y) = (rng.sample(side), rng.sample(side)); +//! println!("Point: {}, {}", x, y); +//! } +//! ``` +//! +//! # Extending `Uniform` to support a custom type +//! +//! To extend [`Uniform`] to support your own types, write a back-end which +//! implements the [`UniformSampler`] trait, then implement the [`SampleUniform`] +//! helper trait to "register" your back-end. See the `MyF32` example below. +//! +//! At a minimum, the back-end needs to store any parameters needed for sampling +//! (e.g. the target range) and implement `new`, `new_inclusive` and `sample`. +//! Those methods should include an assertion to check the range is valid (i.e. +//! `low < high`). The example below merely wraps another back-end. +//! +//! The `new`, `new_inclusive`, `sample_single` and `sample_single_inclusive` +//! functions use arguments of +//! type `SampleBorrow` to support passing in values by reference or +//! by value. In the implementation of these functions, you can choose to +//! simply use the reference returned by [`SampleBorrow::borrow`], or you can choose +//! to copy or clone the value, whatever is appropriate for your type. +//! +//! ``` +//! use rand::prelude::*; +//! use rand::distr::uniform::{Uniform, SampleUniform, +//! UniformSampler, UniformFloat, SampleBorrow, Error}; +//! +//! struct MyF32(f32); +//! +//! #[derive(Clone, Copy, Debug)] +//! struct UniformMyF32(UniformFloat); +//! +//! impl UniformSampler for UniformMyF32 { +//! type X = MyF32; +//! +//! fn new(low: B1, high: B2) -> Result +//! where B1: SampleBorrow + Sized, +//! B2: SampleBorrow + Sized +//! { +//! UniformFloat::::new(low.borrow().0, high.borrow().0).map(UniformMyF32) +//! } +//! fn new_inclusive(low: B1, high: B2) -> Result +//! where B1: SampleBorrow + Sized, +//! B2: SampleBorrow + Sized +//! { +//! UniformFloat::::new_inclusive(low.borrow().0, high.borrow().0).map(UniformMyF32) +//! } +//! fn sample(&self, rng: &mut R) -> Self::X { +//! MyF32(self.0.sample(rng)) +//! } +//! } +//! +//! impl SampleUniform for MyF32 { +//! type Sampler = UniformMyF32; +//! } +//! +//! let (low, high) = (MyF32(17.0f32), MyF32(22.0f32)); +//! let uniform = Uniform::new(low, high).unwrap(); +//! let x = uniform.sample(&mut thread_rng()); +//! ``` +//! +//! [`SampleUniform`]: crate::distr::uniform::SampleUniform +//! [`UniformSampler`]: crate::distr::uniform::UniformSampler +//! [`UniformInt`]: crate::distr::uniform::UniformInt +//! [`UniformFloat`]: crate::distr::uniform::UniformFloat +//! [`UniformDuration`]: crate::distr::uniform::UniformDuration +//! [`SampleBorrow::borrow`]: crate::distr::uniform::SampleBorrow::borrow + +#[path = "uniform_float.rs"] +mod float; +#[doc(inline)] +pub use float::UniformFloat; + +#[path = "uniform_int.rs"] +mod int; +#[doc(inline)] +pub use int::UniformInt; + +#[path = "uniform_other.rs"] +mod other; +#[doc(inline)] +pub use other::{UniformChar, UniformDuration}; + +use core::fmt; +use core::ops::{Range, RangeInclusive}; + +use crate::distr::Distribution; +use crate::{Rng, RngCore}; + +/// Error type returned from [`Uniform::new`] and `new_inclusive`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// `low > high`, or equal in case of exclusive range. + EmptyRange, + /// Input or range `high - low` is non-finite. Not relevant to integer types. + NonFinite, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Error::EmptyRange => "low > high (or equal if exclusive) in uniform distribution", + Error::NonFinite => "Non-finite range in uniform distribution", + }) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// Sample values uniformly between two bounds. +/// +/// [`Uniform::new`] and [`Uniform::new_inclusive`] construct a uniform +/// distribution sampling from the given range; these functions may do extra +/// work up front to make sampling of multiple values faster. If only one sample +/// from the range is required, [`Rng::gen_range`] can be more efficient. +/// +/// When sampling from a constant range, many calculations can happen at +/// compile-time and all methods should be fast; for floating-point ranges and +/// the full range of integer types, this should have comparable performance to +/// the `Standard` distribution. +/// +/// Steps are taken to avoid bias, which might be present in naive +/// implementations; for example `rng.gen::() % 170` samples from the range +/// `[0, 169]` but is twice as likely to select numbers less than 85 than other +/// values. Further, the implementations here give more weight to the high-bits +/// generated by the RNG than the low bits, since with some RNGs the low-bits +/// are of lower quality than the high bits. +/// +/// Implementations must sample in `[low, high)` range for +/// `Uniform::new(low, high)`, i.e., excluding `high`. In particular, care must +/// be taken to ensure that rounding never results values `< low` or `>= high`. +/// +/// # Example +/// +/// ``` +/// use rand::distr::{Distribution, Uniform}; +/// +/// let between = Uniform::try_from(10..10000).unwrap(); +/// let mut rng = rand::thread_rng(); +/// let mut sum = 0; +/// for _ in 0..1000 { +/// sum += between.sample(&mut rng); +/// } +/// println!("{}", sum); +/// ``` +/// +/// For a single sample, [`Rng::gen_range`] may be preferred: +/// +/// ``` +/// use rand::Rng; +/// +/// let mut rng = rand::thread_rng(); +/// println!("{}", rng.gen_range(0..10)); +/// ``` +/// +/// [`new`]: Uniform::new +/// [`new_inclusive`]: Uniform::new_inclusive +/// [`Rng::gen_range`]: Rng::gen_range +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] +#[cfg_attr( + feature = "serde1", + serde(bound(deserialize = "X::Sampler: Deserialize<'de>")) +)] +pub struct Uniform(X::Sampler); + +impl Uniform { + /// Create a new `Uniform` instance, which samples uniformly from the half + /// open range `[low, high)` (excluding `high`). + /// + /// For discrete types (e.g. integers), samples will always be strictly less + /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), + /// samples may equal `high` due to loss of precision but may not be + /// greater than `high`. + /// + /// Fails if `low >= high`, or if `low`, `high` or the range `high - low` is + /// non-finite. In release mode, only the range is checked. + pub fn new(low: B1, high: B2) -> Result, Error> + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + X::Sampler::new(low, high).map(Uniform) + } + + /// Create a new `Uniform` instance, which samples uniformly from the closed + /// range `[low, high]` (inclusive). + /// + /// Fails if `low > high`, or if `low`, `high` or the range `high - low` is + /// non-finite. In release mode, only the range is checked. + pub fn new_inclusive(low: B1, high: B2) -> Result, Error> + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + X::Sampler::new_inclusive(low, high).map(Uniform) + } +} + +impl Distribution for Uniform { + fn sample(&self, rng: &mut R) -> X { + self.0.sample(rng) + } +} + +/// Helper trait for creating objects using the correct implementation of +/// [`UniformSampler`] for the sampling type. +/// +/// See the [module documentation] on how to implement [`Uniform`] range +/// sampling for a custom type. +/// +/// [module documentation]: crate::distr::uniform +pub trait SampleUniform: Sized { + /// The `UniformSampler` implementation supporting type `X`. + type Sampler: UniformSampler; +} + +/// Helper trait handling actual uniform sampling. +/// +/// See the [module documentation] on how to implement [`Uniform`] range +/// sampling for a custom type. +/// +/// Implementation of [`sample_single`] is optional, and is only useful when +/// the implementation can be faster than `Self::new(low, high).sample(rng)`. +/// +/// [module documentation]: crate::distr::uniform +/// [`sample_single`]: UniformSampler::sample_single +pub trait UniformSampler: Sized { + /// The type sampled by this implementation. + type X; + + /// Construct self, with inclusive lower bound and exclusive upper bound `[low, high)`. + /// + /// For discrete types (e.g. integers), samples will always be strictly less + /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), + /// samples may equal `high` due to loss of precision but may not be + /// greater than `high`. + /// + /// Usually users should not call this directly but prefer to use + /// [`Uniform::new`]. + fn new(low: B1, high: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized; + + /// Construct self, with inclusive bounds `[low, high]`. + /// + /// Usually users should not call this directly but prefer to use + /// [`Uniform::new_inclusive`]. + fn new_inclusive(low: B1, high: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized; + + /// Sample a value. + fn sample(&self, rng: &mut R) -> Self::X; + + /// Sample a single value uniformly from a range with inclusive lower bound + /// and exclusive upper bound `[low, high)`. + /// + /// For discrete types (e.g. integers), samples will always be strictly less + /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), + /// samples may equal `high` due to loss of precision but may not be + /// greater than `high`. + /// + /// By default this is implemented using + /// `UniformSampler::new(low, high).sample(rng)`. However, for some types + /// more optimal implementations for single usage may be provided via this + /// method (which is the case for integers and floats). + /// Results may not be identical. + /// + /// Note that to use this method in a generic context, the type needs to be + /// retrieved via `SampleUniform::Sampler` as follows: + /// ``` + /// use rand::{thread_rng, distr::uniform::{SampleUniform, UniformSampler}}; + /// # #[allow(unused)] + /// fn sample_from_range(lb: T, ub: T) -> T { + /// let mut rng = thread_rng(); + /// ::Sampler::sample_single(lb, ub, &mut rng).unwrap() + /// } + /// ``` + fn sample_single( + low: B1, + high: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let uniform: Self = UniformSampler::new(low, high)?; + Ok(uniform.sample(rng)) + } + + /// Sample a single value uniformly from a range with inclusive lower bound + /// and inclusive upper bound `[low, high]`. + /// + /// By default this is implemented using + /// `UniformSampler::new_inclusive(low, high).sample(rng)`. However, for + /// some types more optimal implementations for single usage may be provided + /// via this method. + /// Results may not be identical. + fn sample_single_inclusive( + low: B1, + high: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let uniform: Self = UniformSampler::new_inclusive(low, high)?; + Ok(uniform.sample(rng)) + } +} + +impl TryFrom> for Uniform { + type Error = Error; + + fn try_from(r: Range) -> Result, Error> { + Uniform::new(r.start, r.end) + } +} + +impl TryFrom> for Uniform { + type Error = Error; + + fn try_from(r: ::core::ops::RangeInclusive) -> Result, Error> { + Uniform::new_inclusive(r.start(), r.end()) + } +} + +/// Helper trait similar to [`Borrow`] but implemented +/// only for [`SampleUniform`] and references to [`SampleUniform`] +/// in order to resolve ambiguity issues. +/// +/// [`Borrow`]: std::borrow::Borrow +pub trait SampleBorrow { + /// Immutably borrows from an owned value. See [`Borrow::borrow`] + /// + /// [`Borrow::borrow`]: std::borrow::Borrow::borrow + fn borrow(&self) -> &Borrowed; +} +impl SampleBorrow for Borrowed +where + Borrowed: SampleUniform, +{ + #[inline(always)] + fn borrow(&self) -> &Borrowed { + self + } +} +impl<'a, Borrowed> SampleBorrow for &'a Borrowed +where + Borrowed: SampleUniform, +{ + #[inline(always)] + fn borrow(&self) -> &Borrowed { + self + } +} + +/// Range that supports generating a single sample efficiently. +/// +/// Any type implementing this trait can be used to specify the sampled range +/// for `Rng::gen_range`. +pub trait SampleRange { + /// Generate a sample from the given range. + fn sample_single(self, rng: &mut R) -> Result; + + /// Check whether the range is empty. + fn is_empty(&self) -> bool; +} + +impl SampleRange for Range { + #[inline] + fn sample_single(self, rng: &mut R) -> Result { + T::Sampler::sample_single(self.start, self.end, rng) + } + + #[inline] + fn is_empty(&self) -> bool { + !(self.start < self.end) + } +} + +impl SampleRange for RangeInclusive { + #[inline] + fn sample_single(self, rng: &mut R) -> Result { + T::Sampler::sample_single_inclusive(self.start(), self.end(), rng) + } + + #[inline] + fn is_empty(&self) -> bool { + !(self.start() <= self.end()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::time::Duration; + + #[test] + #[cfg(feature = "serde1")] + fn test_uniform_serialization() { + let unit_box: Uniform = Uniform::new(-1, 1).unwrap(); + let de_unit_box: Uniform = + bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); + assert_eq!(unit_box.0, de_unit_box.0); + + let unit_box: Uniform = Uniform::new(-1., 1.).unwrap(); + let de_unit_box: Uniform = + bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); + assert_eq!(unit_box.0, de_unit_box.0); + } + + #[test] + fn test_custom_uniform() { + use crate::distr::uniform::{SampleBorrow, SampleUniform, UniformFloat, UniformSampler}; + #[derive(Clone, Copy, PartialEq, PartialOrd)] + struct MyF32 { + x: f32, + } + #[derive(Clone, Copy, Debug)] + struct UniformMyF32(UniformFloat); + impl UniformSampler for UniformMyF32 { + type X = MyF32; + + fn new(low: B1, high: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + UniformFloat::::new(low.borrow().x, high.borrow().x).map(UniformMyF32) + } + + fn new_inclusive(low: B1, high: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + UniformSampler::new(low, high) + } + + fn sample(&self, rng: &mut R) -> Self::X { + MyF32 { + x: self.0.sample(rng), + } + } + } + impl SampleUniform for MyF32 { + type Sampler = UniformMyF32; + } + + let (low, high) = (MyF32 { x: 17.0f32 }, MyF32 { x: 22.0f32 }); + let uniform = Uniform::new(low, high).unwrap(); + let mut rng = crate::test::rng(804); + for _ in 0..100 { + let x: MyF32 = rng.sample(uniform); + assert!(low <= x && x < high); + } + } + + #[test] + fn value_stability() { + fn test_samples( + lb: T, + ub: T, + expected_single: &[T], + expected_multiple: &[T], + ) where + Uniform: Distribution, + { + let mut rng = crate::test::rng(897); + let mut buf = [lb; 3]; + + for x in &mut buf { + *x = T::Sampler::sample_single(lb, ub, &mut rng).unwrap(); + } + assert_eq!(&buf, expected_single); + + let distr = Uniform::new(lb, ub).unwrap(); + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(&buf, expected_multiple); + } + + // We test on a sub-set of types; possibly we should do more. + // TODO: SIMD types + + test_samples(11u8, 219, &[17, 66, 214], &[181, 93, 165]); + test_samples(11u32, 219, &[17, 66, 214], &[181, 93, 165]); + + test_samples( + 0f32, + 1e-2f32, + &[0.0003070104, 0.0026630748, 0.00979833], + &[0.008194133, 0.00398172, 0.007428536], + ); + test_samples( + -1e10f64, + 1e10f64, + &[-4673848682.871551, 6388267422.932352, 4857075081.198343], + &[1173375212.1808167, 1917642852.109581, 2365076174.3153973], + ); + + test_samples( + Duration::new(2, 0), + Duration::new(4, 0), + &[ + Duration::new(2, 532615131), + Duration::new(3, 638826742), + Duration::new(3, 485707508), + ], + &[ + Duration::new(3, 117337521), + Duration::new(3, 191764285), + Duration::new(3, 236507617), + ], + ); + } + + #[test] + fn uniform_distributions_can_be_compared() { + assert_eq!( + Uniform::new(1.0, 2.0).unwrap(), + Uniform::new(1.0, 2.0).unwrap() + ); + + // To cover UniformInt + assert_eq!( + Uniform::new(1_u32, 2_u32).unwrap(), + Uniform::new(1_u32, 2_u32).unwrap() + ); + } +} diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs new file mode 100644 index 00000000000..b44e192c65d --- /dev/null +++ b/src/distr/uniform_float.rs @@ -0,0 +1,450 @@ +// Copyright 2018-2020 Developers of the Rand project. +// Copyright 2017 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! `UniformFloat` implementation + +use super::{Error, SampleBorrow, SampleUniform, UniformSampler}; +use crate::distr::float::IntoFloat; +use crate::distr::utils::{BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD}; +use crate::Rng; + +#[cfg(feature = "simd_support")] +use core::simd::prelude::*; +// #[cfg(feature = "simd_support")] +// use core::simd::{LaneCount, SupportedLaneCount}; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// The back-end implementing [`UniformSampler`] for floating-point types. +/// +/// Unless you are implementing [`UniformSampler`] for your own type, this type +/// should not be used directly, use [`Uniform`] instead. +/// +/// # Implementation notes +/// +/// Instead of generating a float in the `[0, 1)` range using [`Standard`], the +/// `UniformFloat` implementation converts the output of an PRNG itself. This +/// way one or two steps can be optimized out. +/// +/// The floats are first converted to a value in the `[1, 2)` interval using a +/// transmute-based method, and then mapped to the expected range with a +/// multiply and addition. Values produced this way have what equals 23 bits of +/// random digits for an `f32`, and 52 for an `f64`. +/// +/// [`new`]: UniformSampler::new +/// [`new_inclusive`]: UniformSampler::new_inclusive +/// [`Standard`]: crate::distr::Standard +/// [`Uniform`]: super::Uniform +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct UniformFloat { + low: X, + scale: X, +} + +macro_rules! uniform_float_impl { + ($($meta:meta)?, $ty:ty, $uty:ident, $f_scalar:ident, $u_scalar:ident, $bits_to_discard:expr) => { + $(#[cfg($meta)])? + impl UniformFloat<$ty> { + /// Construct, reducing `scale` as required to ensure that rounding + /// can never yield values greater than `high`. + /// + /// Note: though it may be tempting to use a variant of this method + /// to ensure that samples from `[low, high)` are always strictly + /// less than `high`, this approach may be very slow where + /// `scale.abs()` is much smaller than `high.abs()` + /// (example: `low=0.99999999997819644, high=1.`). + fn new_bounded(low: $ty, high: $ty, mut scale: $ty) -> Self { + let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); + + loop { + let mask = (scale * max_rand + low).gt_mask(high); + if !mask.any() { + break; + } + scale = scale.decrease_masked(mask); + } + + debug_assert!(<$ty>::splat(0.0).all_le(scale)); + + UniformFloat { low, scale } + } + } + + $(#[cfg($meta)])? + impl SampleUniform for $ty { + type Sampler = UniformFloat<$ty>; + } + + $(#[cfg($meta)])? + impl UniformSampler for UniformFloat<$ty> { + type X = $ty; + + fn new(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + #[cfg(debug_assertions)] + if !(low.all_finite()) || !(high.all_finite()) { + return Err(Error::NonFinite); + } + if !(low.all_lt(high)) { + return Err(Error::EmptyRange); + } + + let scale = high - low; + if !(scale.all_finite()) { + return Err(Error::NonFinite); + } + + Ok(Self::new_bounded(low, high, scale)) + } + + fn new_inclusive(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + #[cfg(debug_assertions)] + if !(low.all_finite()) || !(high.all_finite()) { + return Err(Error::NonFinite); + } + if !low.all_le(high) { + return Err(Error::EmptyRange); + } + + let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); + let scale = (high - low) / max_rand; + if !scale.all_finite() { + return Err(Error::NonFinite); + } + + Ok(Self::new_bounded(low, high, scale)) + } + + fn sample(&self, rng: &mut R) -> Self::X { + // Generate a value in the range [1, 2) + let value1_2 = (rng.random::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); + + // Get a value in the range [0, 1) to avoid overflow when multiplying by scale + let value0_1 = value1_2 - <$ty>::splat(1.0); + + // We don't use `f64::mul_add`, because it is not available with + // `no_std`. Furthermore, it is slower for some targets (but + // faster for others). However, the order of multiplication and + // addition is important, because on some platforms (e.g. ARM) + // it will be optimized to a single (non-FMA) instruction. + value0_1 * self.scale + self.low + } + + #[inline] + fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + Self::sample_single_inclusive(low_b, high_b, rng) + } + + #[inline] + fn sample_single_inclusive(low_b: B1, high_b: B2, rng: &mut R) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + #[cfg(debug_assertions)] + if !low.all_finite() || !high.all_finite() { + return Err(Error::NonFinite); + } + if !low.all_le(high) { + return Err(Error::EmptyRange); + } + let scale = high - low; + if !scale.all_finite() { + return Err(Error::NonFinite); + } + + // Generate a value in the range [1, 2) + let value1_2 = + (rng.random::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); + + // Get a value in the range [0, 1) to avoid overflow when multiplying by scale + let value0_1 = value1_2 - <$ty>::splat(1.0); + + // Doing multiply before addition allows some architectures + // to use a single instruction. + Ok(value0_1 * scale + low) + } + } + }; +} + +uniform_float_impl! { , f32, u32, f32, u32, 32 - 23 } +uniform_float_impl! { , f64, u64, f64, u64, 64 - 52 } + +#[cfg(feature = "simd_support")] +uniform_float_impl! { feature = "simd_support", f32x2, u32x2, f32, u32, 32 - 23 } +#[cfg(feature = "simd_support")] +uniform_float_impl! { feature = "simd_support", f32x4, u32x4, f32, u32, 32 - 23 } +#[cfg(feature = "simd_support")] +uniform_float_impl! { feature = "simd_support", f32x8, u32x8, f32, u32, 32 - 23 } +#[cfg(feature = "simd_support")] +uniform_float_impl! { feature = "simd_support", f32x16, u32x16, f32, u32, 32 - 23 } + +#[cfg(feature = "simd_support")] +uniform_float_impl! { feature = "simd_support", f64x2, u64x2, f64, u64, 64 - 52 } +#[cfg(feature = "simd_support")] +uniform_float_impl! { feature = "simd_support", f64x4, u64x4, f64, u64, 64 - 52 } +#[cfg(feature = "simd_support")] +uniform_float_impl! { feature = "simd_support", f64x8, u64x8, f64, u64, 64 - 52 } + +#[cfg(test)] +mod tests { + use super::*; + use crate::distr::{utils::FloatSIMDScalarUtils, Uniform}; + use crate::rngs::mock::StepRng; + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_floats() { + let mut rng = crate::test::rng(252); + let mut zero_rng = StepRng::new(0, 0); + let mut max_rng = StepRng::new(0xffff_ffff_ffff_ffff, 0); + macro_rules! t { + ($ty:ty, $f_scalar:ident, $bits_shifted:expr) => {{ + let v: &[($f_scalar, $f_scalar)] = &[ + (0.0, 100.0), + (-1e35, -1e25), + (1e-35, 1e-25), + (-1e35, 1e35), + (<$f_scalar>::from_bits(0), <$f_scalar>::from_bits(3)), + (-<$f_scalar>::from_bits(10), -<$f_scalar>::from_bits(1)), + (-<$f_scalar>::from_bits(5), 0.0), + (-<$f_scalar>::from_bits(7), -0.0), + (0.1 * $f_scalar::MAX, $f_scalar::MAX), + (-$f_scalar::MAX * 0.2, $f_scalar::MAX * 0.7), + ]; + for &(low_scalar, high_scalar) in v.iter() { + for lane in 0..<$ty>::LEN { + let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); + let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); + let my_uniform = Uniform::new(low, high).unwrap(); + let my_incl_uniform = Uniform::new_inclusive(low, high).unwrap(); + for _ in 0..100 { + let v = rng.sample(my_uniform).extract(lane); + assert!(low_scalar <= v && v <= high_scalar); + let v = rng.sample(my_incl_uniform).extract(lane); + assert!(low_scalar <= v && v <= high_scalar); + let v = + <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng) + .unwrap() + .extract(lane); + assert!(low_scalar <= v && v <= high_scalar); + let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive( + low, high, &mut rng, + ) + .unwrap() + .extract(lane); + assert!(low_scalar <= v && v <= high_scalar); + } + + assert_eq!( + rng.sample(Uniform::new_inclusive(low, low).unwrap()) + .extract(lane), + low_scalar + ); + + assert_eq!(zero_rng.sample(my_uniform).extract(lane), low_scalar); + assert_eq!(zero_rng.sample(my_incl_uniform).extract(lane), low_scalar); + assert_eq!( + <$ty as SampleUniform>::Sampler::sample_single( + low, + high, + &mut zero_rng + ) + .unwrap() + .extract(lane), + low_scalar + ); + assert_eq!( + <$ty as SampleUniform>::Sampler::sample_single_inclusive( + low, + high, + &mut zero_rng + ) + .unwrap() + .extract(lane), + low_scalar + ); + + assert!(max_rng.sample(my_uniform).extract(lane) <= high_scalar); + assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar); + // sample_single cannot cope with max_rng: + // assert!(<$ty as SampleUniform>::Sampler + // ::sample_single(low, high, &mut max_rng).unwrap() + // .extract(lane) <= high_scalar); + assert!( + <$ty as SampleUniform>::Sampler::sample_single_inclusive( + low, + high, + &mut max_rng + ) + .unwrap() + .extract(lane) + <= high_scalar + ); + + // Don't run this test for really tiny differences between high and low + // since for those rounding might result in selecting high for a very + // long time. + if (high_scalar - low_scalar) > 0.0001 { + let mut lowering_max_rng = StepRng::new( + 0xffff_ffff_ffff_ffff, + (-1i64 << $bits_shifted) as u64, + ); + assert!( + <$ty as SampleUniform>::Sampler::sample_single( + low, + high, + &mut lowering_max_rng + ) + .unwrap() + .extract(lane) + <= high_scalar + ); + } + } + } + + assert_eq!( + rng.sample(Uniform::new_inclusive($f_scalar::MAX, $f_scalar::MAX).unwrap()), + $f_scalar::MAX + ); + assert_eq!( + rng.sample(Uniform::new_inclusive(-$f_scalar::MAX, -$f_scalar::MAX).unwrap()), + -$f_scalar::MAX + ); + }}; + } + + t!(f32, f32, 32 - 23); + t!(f64, f64, 64 - 52); + #[cfg(feature = "simd_support")] + { + t!(f32x2, f32, 32 - 23); + t!(f32x4, f32, 32 - 23); + t!(f32x8, f32, 32 - 23); + t!(f32x16, f32, 32 - 23); + t!(f64x2, f64, 64 - 52); + t!(f64x4, f64, 64 - 52); + t!(f64x8, f64, 64 - 52); + } + } + + #[test] + fn test_float_overflow() { + assert_eq!(Uniform::try_from(f64::MIN..f64::MAX), Err(Error::NonFinite)); + } + + #[test] + #[should_panic] + fn test_float_overflow_single() { + let mut rng = crate::test::rng(252); + rng.gen_range(f64::MIN..f64::MAX); + } + + #[test] + #[cfg(all(feature = "std", panic = "unwind"))] + fn test_float_assertions() { + use super::SampleUniform; + fn range(low: T, high: T) -> Result { + let mut rng = crate::test::rng(253); + T::Sampler::sample_single(low, high, &mut rng) + } + + macro_rules! t { + ($ty:ident, $f_scalar:ident) => {{ + let v: &[($f_scalar, $f_scalar)] = &[ + ($f_scalar::NAN, 0.0), + (1.0, $f_scalar::NAN), + ($f_scalar::NAN, $f_scalar::NAN), + (1.0, 0.5), + ($f_scalar::MAX, -$f_scalar::MAX), + ($f_scalar::INFINITY, $f_scalar::INFINITY), + ($f_scalar::NEG_INFINITY, $f_scalar::NEG_INFINITY), + ($f_scalar::NEG_INFINITY, 5.0), + (5.0, $f_scalar::INFINITY), + ($f_scalar::NAN, $f_scalar::INFINITY), + ($f_scalar::NEG_INFINITY, $f_scalar::NAN), + ($f_scalar::NEG_INFINITY, $f_scalar::INFINITY), + ]; + for &(low_scalar, high_scalar) in v.iter() { + for lane in 0..<$ty>::LEN { + let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); + let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); + assert!(range(low, high).is_err()); + assert!(Uniform::new(low, high).is_err()); + assert!(Uniform::new_inclusive(low, high).is_err()); + assert!(Uniform::new(low, low).is_err()); + } + } + }}; + } + + t!(f32, f32); + t!(f64, f64); + #[cfg(feature = "simd_support")] + { + t!(f32x2, f32); + t!(f32x4, f32); + t!(f32x8, f32); + t!(f32x16, f32); + t!(f64x2, f64); + t!(f64x4, f64); + t!(f64x8, f64); + } + } + + #[test] + fn test_uniform_from_std_range() { + let r = Uniform::try_from(2.0f64..7.0).unwrap(); + assert_eq!(r.0.low, 2.0); + assert_eq!(r.0.scale, 5.0); + } + + #[test] + fn test_uniform_from_std_range_bad_limits() { + #![allow(clippy::reversed_empty_ranges)] + assert!(Uniform::try_from(100.0..10.0).is_err()); + assert!(Uniform::try_from(100.0..100.0).is_err()); + } + + #[test] + fn test_uniform_from_std_range_inclusive() { + let r = Uniform::try_from(2.0f64..=7.0).unwrap(); + assert_eq!(r.0.low, 2.0); + assert!(r.0.scale > 5.0); + assert!(r.0.scale < 5.0 + 1e-14); + } + + #[test] + fn test_uniform_from_std_range_inclusive_bad_limits() { + #![allow(clippy::reversed_empty_ranges)] + assert!(Uniform::try_from(100.0..=10.0).is_err()); + assert!(Uniform::try_from(100.0..=99.0).is_err()); + } +} diff --git a/src/distr/uniform_int.rs b/src/distr/uniform_int.rs new file mode 100644 index 00000000000..5a6254058e0 --- /dev/null +++ b/src/distr/uniform_int.rs @@ -0,0 +1,521 @@ +// Copyright 2018-2020 Developers of the Rand project. +// Copyright 2017 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! `UniformInt` implementation + +use super::{Error, SampleBorrow, SampleUniform, UniformSampler}; +use crate::distr::utils::WideningMultiply; +#[cfg(feature = "simd_support")] +use crate::distr::{Distribution, Standard}; +use crate::Rng; + +#[cfg(feature = "simd_support")] +use core::simd::prelude::*; +#[cfg(feature = "simd_support")] +use core::simd::{LaneCount, SupportedLaneCount}; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +/// The back-end implementing [`UniformSampler`] for integer types. +/// +/// Unless you are implementing [`UniformSampler`] for your own type, this type +/// should not be used directly, use [`Uniform`] instead. +/// +/// # Implementation notes +/// +/// For simplicity, we use the same generic struct `UniformInt` for all +/// integer types `X`. This gives us only one field type, `X`; to store unsigned +/// values of this size, we take use the fact that these conversions are no-ops. +/// +/// For a closed range, the number of possible numbers we should generate is +/// `range = (high - low + 1)`. To avoid bias, we must ensure that the size of +/// our sample space, `zone`, is a multiple of `range`; other values must be +/// rejected (by replacing with a new random sample). +/// +/// As a special case, we use `range = 0` to represent the full range of the +/// result type (i.e. for `new_inclusive($ty::MIN, $ty::MAX)`). +/// +/// The optimum `zone` is the largest product of `range` which fits in our +/// (unsigned) target type. We calculate this by calculating how many numbers we +/// must reject: `reject = (MAX + 1) % range = (MAX - range + 1) % range`. Any (large) +/// product of `range` will suffice, thus in `sample_single` we multiply by a +/// power of 2 via bit-shifting (faster but may cause more rejections). +/// +/// The smallest integer PRNGs generate is `u32`. For 8- and 16-bit outputs we +/// use `u32` for our `zone` and samples (because it's not slower and because +/// it reduces the chance of having to reject a sample). In this case we cannot +/// store `zone` in the target type since it is too large, however we know +/// `ints_to_reject < range <= $uty::MAX`. +/// +/// An alternative to using a modulus is widening multiply: After a widening +/// multiply by `range`, the result is in the high word. Then comparing the low +/// word against `zone` makes sure our distribution is uniform. +/// +/// [`Uniform`]: super::Uniform +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct UniformInt { + pub(super) low: X, + pub(super) range: X, + thresh: X, // effectively 2.pow(max(64, uty_bits)) % range +} + +macro_rules! uniform_int_impl { + ($ty:ty, $uty:ty, $sample_ty:ident) => { + impl SampleUniform for $ty { + type Sampler = UniformInt<$ty>; + } + + impl UniformSampler for UniformInt<$ty> { + // We play free and fast with unsigned vs signed here + // (when $ty is signed), but that's fine, since the + // contract of this macro is for $ty and $uty to be + // "bit-equal", so casting between them is a no-op. + + type X = $ty; + + #[inline] // if the range is constant, this helps LLVM to do the + // calculations at compile-time. + fn new(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low < high) { + return Err(Error::EmptyRange); + } + UniformSampler::new_inclusive(low, high - 1) + } + + #[inline] // if the range is constant, this helps LLVM to do the + // calculations at compile-time. + fn new_inclusive(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low <= high) { + return Err(Error::EmptyRange); + } + + let range = high.wrapping_sub(low).wrapping_add(1) as $uty; + let thresh = if range > 0 { + let range = $sample_ty::from(range); + (range.wrapping_neg() % range) + } else { + 0 + }; + + Ok(UniformInt { + low, + range: range as $ty, // type: $uty + thresh: thresh as $uty as $ty, // type: $sample_ty + }) + } + + /// Sample from distribution, Lemire's method, unbiased + #[inline] + fn sample(&self, rng: &mut R) -> Self::X { + let range = self.range as $uty as $sample_ty; + if range == 0 { + return rng.random(); + } + + let thresh = self.thresh as $uty as $sample_ty; + let hi = loop { + let (hi, lo) = rng.random::<$sample_ty>().wmul(range); + if lo >= thresh { + break hi; + } + }; + self.low.wrapping_add(hi as $ty) + } + + #[inline] + fn sample_single( + low_b: B1, + high_b: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low < high) { + return Err(Error::EmptyRange); + } + Self::sample_single_inclusive(low, high - 1, rng) + } + + /// Sample single value, Canon's method, biased + /// + /// In the worst case, bias affects 1 in `2^n` samples where n is + /// 56 (`i8`), 48 (`i16`), 96 (`i32`), 64 (`i64`), 128 (`i128`). + #[cfg(not(feature = "unbiased"))] + #[inline] + fn sample_single_inclusive( + low_b: B1, + high_b: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low <= high) { + return Err(Error::EmptyRange); + } + let range = high.wrapping_sub(low).wrapping_add(1) as $uty as $sample_ty; + if range == 0 { + // Range is MAX+1 (unrepresentable), so we need a special case + return Ok(rng.random()); + } + + // generate a sample using a sensible integer type + let (mut result, lo_order) = rng.random::<$sample_ty>().wmul(range); + + // if the sample is biased... + if lo_order > range.wrapping_neg() { + // ...generate a new sample to reduce bias... + let (new_hi_order, _) = (rng.random::<$sample_ty>()).wmul(range as $sample_ty); + // ... incrementing result on overflow + let is_overflow = lo_order.checked_add(new_hi_order as $sample_ty).is_none(); + result += is_overflow as $sample_ty; + } + + Ok(low.wrapping_add(result as $ty)) + } + + /// Sample single value, Canon's method, unbiased + #[cfg(feature = "unbiased")] + #[inline] + fn sample_single_inclusive( + low_b: B1, + high_b: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow<$ty> + Sized, + B2: SampleBorrow<$ty> + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low <= high) { + return Err(Error::EmptyRange); + } + let range = high.wrapping_sub(low).wrapping_add(1) as $uty as $sample_ty; + if range == 0 { + // Range is MAX+1 (unrepresentable), so we need a special case + return Ok(rng.random()); + } + + let (mut result, mut lo) = rng.random::<$sample_ty>().wmul(range); + + // In contrast to the biased sampler, we use a loop: + while lo > range.wrapping_neg() { + let (new_hi, new_lo) = (rng.random::<$sample_ty>()).wmul(range); + match lo.checked_add(new_hi) { + Some(x) if x < $sample_ty::MAX => { + // Anything less than MAX: last term is 0 + break; + } + None => { + // Overflow: last term is 1 + result += 1; + break; + } + _ => { + // Unlikely case: must check next sample + lo = new_lo; + continue; + } + } + } + + Ok(low.wrapping_add(result as $ty)) + } + } + }; +} + +uniform_int_impl! { i8, u8, u32 } +uniform_int_impl! { i16, u16, u32 } +uniform_int_impl! { i32, u32, u32 } +uniform_int_impl! { i64, u64, u64 } +uniform_int_impl! { i128, u128, u128 } +uniform_int_impl! { isize, usize, usize } +uniform_int_impl! { u8, u8, u32 } +uniform_int_impl! { u16, u16, u32 } +uniform_int_impl! { u32, u32, u32 } +uniform_int_impl! { u64, u64, u64 } +uniform_int_impl! { usize, usize, usize } +uniform_int_impl! { u128, u128, u128 } + +#[cfg(feature = "simd_support")] +macro_rules! uniform_simd_int_impl { + ($ty:ident, $unsigned:ident) => { + // The "pick the largest zone that can fit in an `u32`" optimization + // is less useful here. Multiple lanes complicate things, we don't + // know the PRNG's minimal output size, and casting to a larger vector + // is generally a bad idea for SIMD performance. The user can still + // implement it manually. + + #[cfg(feature = "simd_support")] + impl SampleUniform for Simd<$ty, LANES> + where + LaneCount: SupportedLaneCount, + Simd<$unsigned, LANES>: + WideningMultiply, Simd<$unsigned, LANES>)>, + Standard: Distribution>, + { + type Sampler = UniformInt>; + } + + #[cfg(feature = "simd_support")] + impl UniformSampler for UniformInt> + where + LaneCount: SupportedLaneCount, + Simd<$unsigned, LANES>: + WideningMultiply, Simd<$unsigned, LANES>)>, + Standard: Distribution>, + { + type X = Simd<$ty, LANES>; + + #[inline] // if the range is constant, this helps LLVM to do the + // calculations at compile-time. + fn new(low_b: B1, high_b: B2) -> Result + where B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low.simd_lt(high).all()) { + return Err(Error::EmptyRange); + } + UniformSampler::new_inclusive(low, high - Simd::splat(1)) + } + + #[inline] // if the range is constant, this helps LLVM to do the + // calculations at compile-time. + fn new_inclusive(low_b: B1, high_b: B2) -> Result + where B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low.simd_le(high).all()) { + return Err(Error::EmptyRange); + } + + // NOTE: all `Simd` operations are inherently wrapping, + // see https://doc.rust-lang.org/std/simd/struct.Simd.html + let range: Simd<$unsigned, LANES> = ((high - low) + Simd::splat(1)).cast(); + + // We must avoid divide-by-zero by using 0 % 1 == 0. + let not_full_range = range.simd_gt(Simd::splat(0)); + let modulo = not_full_range.select(range, Simd::splat(1)); + let ints_to_reject = range.wrapping_neg() % modulo; + + Ok(UniformInt { + low, + // These are really $unsigned values, but store as $ty: + range: range.cast(), + thresh: ints_to_reject.cast(), + }) + } + + fn sample(&self, rng: &mut R) -> Self::X { + let range: Simd<$unsigned, LANES> = self.range.cast(); + let thresh: Simd<$unsigned, LANES> = self.thresh.cast(); + + // This might seem very slow, generating a whole new + // SIMD vector for every sample rejection. For most uses + // though, the chance of rejection is small and provides good + // general performance. With multiple lanes, that chance is + // multiplied. To mitigate this, we replace only the lanes of + // the vector which fail, iteratively reducing the chance of + // rejection. The replacement method does however add a little + // overhead. Benchmarking or calculating probabilities might + // reveal contexts where this replacement method is slower. + let mut v: Simd<$unsigned, LANES> = rng.random(); + loop { + let (hi, lo) = v.wmul(range); + let mask = lo.simd_ge(thresh); + if mask.all() { + let hi: Simd<$ty, LANES> = hi.cast(); + // wrapping addition + let result = self.low + hi; + // `select` here compiles to a blend operation + // When `range.eq(0).none()` the compare and blend + // operations are avoided. + let v: Simd<$ty, LANES> = v.cast(); + return range.simd_gt(Simd::splat(0)).select(result, v); + } + // Replace only the failing lanes + v = mask.select(v, rng.random()); + } + } + } + }; + + // bulk implementation + ($(($unsigned:ident, $signed:ident)),+) => { + $( + uniform_simd_int_impl!($unsigned, $unsigned); + uniform_simd_int_impl!($signed, $unsigned); + )+ + }; +} + +#[cfg(feature = "simd_support")] +uniform_simd_int_impl! { (u8, i8), (u16, i16), (u32, i32), (u64, i64) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::distr::Uniform; + + #[test] + fn test_uniform_bad_limits_equal_int() { + assert_eq!(Uniform::new(10, 10), Err(Error::EmptyRange)); + } + + #[test] + fn test_uniform_good_limits_equal_int() { + let mut rng = crate::test::rng(804); + let dist = Uniform::new_inclusive(10, 10).unwrap(); + for _ in 0..20 { + assert_eq!(rng.sample(dist), 10); + } + } + + #[test] + fn test_uniform_bad_limits_flipped_int() { + assert_eq!(Uniform::new(10, 5), Err(Error::EmptyRange)); + } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_integers() { + let mut rng = crate::test::rng(251); + macro_rules! t { + ($ty:ident, $v:expr, $le:expr, $lt:expr) => {{ + for &(low, high) in $v.iter() { + let my_uniform = Uniform::new(low, high).unwrap(); + for _ in 0..1000 { + let v: $ty = rng.sample(my_uniform); + assert!($le(low, v) && $lt(v, high)); + } + + let my_uniform = Uniform::new_inclusive(low, high).unwrap(); + for _ in 0..1000 { + let v: $ty = rng.sample(my_uniform); + assert!($le(low, v) && $le(v, high)); + } + + let my_uniform = Uniform::new(&low, high).unwrap(); + for _ in 0..1000 { + let v: $ty = rng.sample(my_uniform); + assert!($le(low, v) && $lt(v, high)); + } + + let my_uniform = Uniform::new_inclusive(&low, &high).unwrap(); + for _ in 0..1000 { + let v: $ty = rng.sample(my_uniform); + assert!($le(low, v) && $le(v, high)); + } + + for _ in 0..1000 { + let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng).unwrap(); + assert!($le(low, v) && $lt(v, high)); + } + + for _ in 0..1000 { + let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng).unwrap(); + assert!($le(low, v) && $le(v, high)); + } + } + }}; + + // scalar bulk + ($($ty:ident),*) => {{ + $(t!( + $ty, + [(0, 10), (10, 127), ($ty::MIN, $ty::MAX)], + |x, y| x <= y, + |x, y| x < y + );)* + }}; + + // simd bulk + ($($ty:ident),* => $scalar:ident) => {{ + $(t!( + $ty, + [ + ($ty::splat(0), $ty::splat(10)), + ($ty::splat(10), $ty::splat(127)), + ($ty::splat($scalar::MIN), $ty::splat($scalar::MAX)), + ], + |x: $ty, y| x.simd_le(y).all(), + |x: $ty, y| x.simd_lt(y).all() + );)* + }}; + } + t!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, i128, u128); + + #[cfg(feature = "simd_support")] + { + t!(u8x4, u8x8, u8x16, u8x32, u8x64 => u8); + t!(i8x4, i8x8, i8x16, i8x32, i8x64 => i8); + t!(u16x2, u16x4, u16x8, u16x16, u16x32 => u16); + t!(i16x2, i16x4, i16x8, i16x16, i16x32 => i16); + t!(u32x2, u32x4, u32x8, u32x16 => u32); + t!(i32x2, i32x4, i32x8, i32x16 => i32); + t!(u64x2, u64x4, u64x8 => u64); + t!(i64x2, i64x4, i64x8 => i64); + } + } + + #[test] + fn test_uniform_from_std_range() { + let r = Uniform::try_from(2u32..7).unwrap(); + assert_eq!(r.0.low, 2); + assert_eq!(r.0.range, 5); + } + + #[test] + fn test_uniform_from_std_range_bad_limits() { + #![allow(clippy::reversed_empty_ranges)] + assert!(Uniform::try_from(100..10).is_err()); + assert!(Uniform::try_from(100..100).is_err()); + } + + #[test] + fn test_uniform_from_std_range_inclusive() { + let r = Uniform::try_from(2u32..=6).unwrap(); + assert_eq!(r.0.low, 2); + assert_eq!(r.0.range, 5); + } + + #[test] + fn test_uniform_from_std_range_inclusive_bad_limits() { + #![allow(clippy::reversed_empty_ranges)] + assert!(Uniform::try_from(100..=10).is_err()); + assert!(Uniform::try_from(100..=99).is_err()); + } +} diff --git a/src/distr/uniform_other.rs b/src/distr/uniform_other.rs new file mode 100644 index 00000000000..af10eed50d1 --- /dev/null +++ b/src/distr/uniform_other.rs @@ -0,0 +1,322 @@ +// Copyright 2018-2020 Developers of the Rand project. +// Copyright 2017 The Rust Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! `UniformChar`, `UniformDuration` implementations + +use super::{Error, SampleBorrow, SampleUniform, Uniform, UniformInt, UniformSampler}; +use crate::distr::Distribution; +use crate::Rng; +use core::time::Duration; + +#[cfg(feature = "serde1")] +use serde::{Deserialize, Serialize}; + +impl SampleUniform for char { + type Sampler = UniformChar; +} + +/// The back-end implementing [`UniformSampler`] for `char`. +/// +/// Unless you are implementing [`UniformSampler`] for your own type, this type +/// should not be used directly, use [`Uniform`] instead. +/// +/// This differs from integer range sampling since the range `0xD800..=0xDFFF` +/// are used for surrogate pairs in UCS and UTF-16, and consequently are not +/// valid Unicode code points. We must therefore avoid sampling values in this +/// range. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct UniformChar { + sampler: UniformInt, +} + +/// UTF-16 surrogate range start +const CHAR_SURROGATE_START: u32 = 0xD800; +/// UTF-16 surrogate range size +const CHAR_SURROGATE_LEN: u32 = 0xE000 - CHAR_SURROGATE_START; + +/// Convert `char` to compressed `u32` +fn char_to_comp_u32(c: char) -> u32 { + match c as u32 { + c if c >= CHAR_SURROGATE_START => c - CHAR_SURROGATE_LEN, + c => c, + } +} + +impl UniformSampler for UniformChar { + type X = char; + + #[inline] // if the range is constant, this helps LLVM to do the + // calculations at compile-time. + fn new(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = char_to_comp_u32(*low_b.borrow()); + let high = char_to_comp_u32(*high_b.borrow()); + let sampler = UniformInt::::new(low, high); + sampler.map(|sampler| UniformChar { sampler }) + } + + #[inline] // if the range is constant, this helps LLVM to do the + // calculations at compile-time. + fn new_inclusive(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = char_to_comp_u32(*low_b.borrow()); + let high = char_to_comp_u32(*high_b.borrow()); + let sampler = UniformInt::::new_inclusive(low, high); + sampler.map(|sampler| UniformChar { sampler }) + } + + fn sample(&self, rng: &mut R) -> Self::X { + let mut x = self.sampler.sample(rng); + if x >= CHAR_SURROGATE_START { + x += CHAR_SURROGATE_LEN; + } + // SAFETY: x must not be in surrogate range or greater than char::MAX. + // This relies on range constructors which accept char arguments. + // Validity of input char values is assumed. + unsafe { core::char::from_u32_unchecked(x) } + } +} + +/// Note: the `String` is potentially left with excess capacity if the range +/// includes non ascii chars; optionally the user may call +/// `string.shrink_to_fit()` afterwards. +#[cfg(feature = "alloc")] +impl crate::distr::DistString for Uniform { + fn append_string( + &self, + rng: &mut R, + string: &mut alloc::string::String, + len: usize, + ) { + // Getting the hi value to assume the required length to reserve in string. + let mut hi = self.0.sampler.low + self.0.sampler.range - 1; + if hi >= CHAR_SURROGATE_START { + hi += CHAR_SURROGATE_LEN; + } + // Get the utf8 length of hi to minimize extra space. + let max_char_len = char::from_u32(hi).map(char::len_utf8).unwrap_or(4); + string.reserve(max_char_len * len); + string.extend(self.sample_iter(rng).take(len)) + } +} + +/// The back-end implementing [`UniformSampler`] for `Duration`. +/// +/// Unless you are implementing [`UniformSampler`] for your own types, this type +/// should not be used directly, use [`Uniform`] instead. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +pub struct UniformDuration { + mode: UniformDurationMode, + offset: u32, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +enum UniformDurationMode { + Small { + secs: u64, + nanos: Uniform, + }, + Medium { + nanos: Uniform, + }, + Large { + max_secs: u64, + max_nanos: u32, + secs: Uniform, + }, +} + +impl SampleUniform for Duration { + type Sampler = UniformDuration; +} + +impl UniformSampler for UniformDuration { + type X = Duration; + + #[inline] + fn new(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low < high) { + return Err(Error::EmptyRange); + } + UniformDuration::new_inclusive(low, high - Duration::new(0, 1)) + } + + #[inline] + fn new_inclusive(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low <= high) { + return Err(Error::EmptyRange); + } + + let low_s = low.as_secs(); + let low_n = low.subsec_nanos(); + let mut high_s = high.as_secs(); + let mut high_n = high.subsec_nanos(); + + if high_n < low_n { + high_s -= 1; + high_n += 1_000_000_000; + } + + let mode = if low_s == high_s { + UniformDurationMode::Small { + secs: low_s, + nanos: Uniform::new_inclusive(low_n, high_n)?, + } + } else { + let max = high_s + .checked_mul(1_000_000_000) + .and_then(|n| n.checked_add(u64::from(high_n))); + + if let Some(higher_bound) = max { + let lower_bound = low_s * 1_000_000_000 + u64::from(low_n); + UniformDurationMode::Medium { + nanos: Uniform::new_inclusive(lower_bound, higher_bound)?, + } + } else { + // An offset is applied to simplify generation of nanoseconds + let max_nanos = high_n - low_n; + UniformDurationMode::Large { + max_secs: high_s, + max_nanos, + secs: Uniform::new_inclusive(low_s, high_s)?, + } + } + }; + Ok(UniformDuration { + mode, + offset: low_n, + }) + } + + #[inline] + fn sample(&self, rng: &mut R) -> Duration { + match self.mode { + UniformDurationMode::Small { secs, nanos } => { + let n = nanos.sample(rng); + Duration::new(secs, n) + } + UniformDurationMode::Medium { nanos } => { + let nanos = nanos.sample(rng); + Duration::new(nanos / 1_000_000_000, (nanos % 1_000_000_000) as u32) + } + UniformDurationMode::Large { + max_secs, + max_nanos, + secs, + } => { + // constant folding means this is at least as fast as `Rng::sample(Range)` + let nano_range = Uniform::new(0, 1_000_000_000).unwrap(); + loop { + let s = secs.sample(rng); + let n = nano_range.sample(rng); + if !(s == max_secs && n > max_nanos) { + let sum = n + self.offset; + break Duration::new(s, sum); + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(feature = "serde1")] + fn test_serialization_uniform_duration() { + let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)).unwrap(); + let de_distr: UniformDuration = + bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); + assert_eq!(distr, de_distr); + } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_char() { + let mut rng = crate::test::rng(891); + let mut max = core::char::from_u32(0).unwrap(); + for _ in 0..100 { + let c = rng.gen_range('A'..='Z'); + assert!(c.is_ascii_uppercase()); + max = max.max(c); + } + assert_eq!(max, 'Z'); + let d = Uniform::new( + core::char::from_u32(0xD7F0).unwrap(), + core::char::from_u32(0xE010).unwrap(), + ) + .unwrap(); + for _ in 0..100 { + let c = d.sample(&mut rng); + assert!((c as u32) < 0xD800 || (c as u32) > 0xDFFF); + } + #[cfg(feature = "alloc")] + { + use crate::distr::DistString; + let string1 = d.sample_string(&mut rng, 100); + assert_eq!(string1.capacity(), 300); + let string2 = Uniform::new( + core::char::from_u32(0x0000).unwrap(), + core::char::from_u32(0x0080).unwrap(), + ) + .unwrap() + .sample_string(&mut rng, 100); + assert_eq!(string2.capacity(), 100); + let string3 = Uniform::new_inclusive( + core::char::from_u32(0x0000).unwrap(), + core::char::from_u32(0x0080).unwrap(), + ) + .unwrap() + .sample_string(&mut rng, 100); + assert_eq!(string3.capacity(), 200); + } + } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_durations() { + let mut rng = crate::test::rng(253); + + let v = &[ + (Duration::new(10, 50000), Duration::new(100, 1234)), + (Duration::new(0, 100), Duration::new(1, 50)), + (Duration::new(0, 0), Duration::new(u64::MAX, 999_999_999)), + ]; + for &(low, high) in v.iter() { + let my_uniform = Uniform::new(low, high).unwrap(); + for _ in 0..1000 { + let v = rng.sample(my_uniform); + assert!(low <= v && v < high); + } + } + } +} diff --git a/src/distributions/utils.rs b/src/distr/utils.rs similarity index 100% rename from src/distributions/utils.rs rename to src/distr/utils.rs diff --git a/src/distributions/weighted_index.rs b/src/distr/weighted_index.rs similarity index 98% rename from src/distributions/weighted_index.rs rename to src/distr/weighted_index.rs index 88fad5a88ad..c0ceefee5d1 100644 --- a/src/distributions/weighted_index.rs +++ b/src/distr/weighted_index.rs @@ -8,8 +8,8 @@ //! Weighted index sampling -use crate::distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler}; -use crate::distributions::Distribution; +use crate::distr::uniform::{SampleBorrow, SampleUniform, UniformSampler}; +use crate::distr::Distribution; use crate::Rng; use core::fmt; @@ -59,7 +59,7 @@ use serde::{Deserialize, Serialize}; /// /// ``` /// use rand::prelude::*; -/// use rand::distributions::WeightedIndex; +/// use rand::distr::WeightedIndex; /// /// let choices = ['a', 'b', 'c']; /// let weights = [2, 1, 1]; @@ -78,7 +78,7 @@ use serde::{Deserialize, Serialize}; /// } /// ``` /// -/// [`Uniform`]: crate::distributions::Uniform +/// [`Uniform`]: crate::distr::Uniform /// [`RngCore`]: crate::RngCore /// [`rand_distr::weighted_alias`]: https://docs.rs/rand_distr/*/rand_distr/weighted_alias/index.html /// [`rand_distr::weighted_tree`]: https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html @@ -101,7 +101,7 @@ impl WeightedIndex { /// - [`WeightError::InsufficientNonZero`] when the sum of all weights is zero. /// - [`WeightError::Overflow`] when the sum of all weights overflows. /// - /// [`Uniform`]: crate::distributions::uniform::Uniform + /// [`Uniform`]: crate::distr::uniform::Uniform pub fn new(weights: I) -> Result, WeightError> where I: IntoIterator, @@ -306,7 +306,7 @@ impl WeightedIndex { /// # Example /// /// ``` - /// use rand::distributions::WeightedIndex; + /// use rand::distr::WeightedIndex; /// /// let weights = [0, 1, 2]; /// let dist = WeightedIndex::new(&weights).unwrap(); @@ -341,7 +341,7 @@ impl WeightedIndex { /// # Example /// /// ``` - /// use rand::distributions::WeightedIndex; + /// use rand::distr::WeightedIndex; /// /// let weights = [1, 2, 3]; /// let mut dist = WeightedIndex::new(&weights).unwrap(); diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs deleted file mode 100644 index 306b0cced65..00000000000 --- a/src/distributions/uniform.rs +++ /dev/null @@ -1,1774 +0,0 @@ -// Copyright 2018-2020 Developers of the Rand project. -// Copyright 2017 The Rust Project Developers. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! A distribution uniformly sampling numbers within a given range. -//! -//! [`Uniform`] is the standard distribution to sample uniformly from a range; -//! e.g. `Uniform::new_inclusive(1, 6).unwrap()` can sample integers from 1 to 6, like a -//! standard die. [`Rng::gen_range`] supports any type supported by [`Uniform`]. -//! -//! This distribution is provided with support for several primitive types -//! (all integer and floating-point types) as well as [`std::time::Duration`], -//! and supports extension to user-defined types via a type-specific *back-end* -//! implementation. -//! -//! The types [`UniformInt`], [`UniformFloat`] and [`UniformDuration`] are the -//! back-ends supporting sampling from primitive integer and floating-point -//! ranges as well as from [`std::time::Duration`]; these types do not normally -//! need to be used directly (unless implementing a derived back-end). -//! -//! # Example usage -//! -//! ``` -//! use rand::{Rng, thread_rng}; -//! use rand::distributions::Uniform; -//! -//! let mut rng = thread_rng(); -//! let side = Uniform::new(-10.0, 10.0).unwrap(); -//! -//! // sample between 1 and 10 points -//! for _ in 0..rng.gen_range(1..=10) { -//! // sample a point from the square with sides -10 - 10 in two dimensions -//! let (x, y) = (rng.sample(side), rng.sample(side)); -//! println!("Point: {}, {}", x, y); -//! } -//! ``` -//! -//! # Extending `Uniform` to support a custom type -//! -//! To extend [`Uniform`] to support your own types, write a back-end which -//! implements the [`UniformSampler`] trait, then implement the [`SampleUniform`] -//! helper trait to "register" your back-end. See the `MyF32` example below. -//! -//! At a minimum, the back-end needs to store any parameters needed for sampling -//! (e.g. the target range) and implement `new`, `new_inclusive` and `sample`. -//! Those methods should include an assertion to check the range is valid (i.e. -//! `low < high`). The example below merely wraps another back-end. -//! -//! The `new`, `new_inclusive`, `sample_single` and `sample_single_inclusive` -//! functions use arguments of -//! type `SampleBorrow` to support passing in values by reference or -//! by value. In the implementation of these functions, you can choose to -//! simply use the reference returned by [`SampleBorrow::borrow`], or you can choose -//! to copy or clone the value, whatever is appropriate for your type. -//! -//! ``` -//! use rand::prelude::*; -//! use rand::distributions::uniform::{Uniform, SampleUniform, -//! UniformSampler, UniformFloat, SampleBorrow, Error}; -//! -//! struct MyF32(f32); -//! -//! #[derive(Clone, Copy, Debug)] -//! struct UniformMyF32(UniformFloat); -//! -//! impl UniformSampler for UniformMyF32 { -//! type X = MyF32; -//! -//! fn new(low: B1, high: B2) -> Result -//! where B1: SampleBorrow + Sized, -//! B2: SampleBorrow + Sized -//! { -//! UniformFloat::::new(low.borrow().0, high.borrow().0).map(UniformMyF32) -//! } -//! fn new_inclusive(low: B1, high: B2) -> Result -//! where B1: SampleBorrow + Sized, -//! B2: SampleBorrow + Sized -//! { -//! UniformFloat::::new_inclusive(low.borrow().0, high.borrow().0).map(UniformMyF32) -//! } -//! fn sample(&self, rng: &mut R) -> Self::X { -//! MyF32(self.0.sample(rng)) -//! } -//! } -//! -//! impl SampleUniform for MyF32 { -//! type Sampler = UniformMyF32; -//! } -//! -//! let (low, high) = (MyF32(17.0f32), MyF32(22.0f32)); -//! let uniform = Uniform::new(low, high).unwrap(); -//! let x = uniform.sample(&mut thread_rng()); -//! ``` -//! -//! [`SampleUniform`]: crate::distributions::uniform::SampleUniform -//! [`UniformSampler`]: crate::distributions::uniform::UniformSampler -//! [`UniformInt`]: crate::distributions::uniform::UniformInt -//! [`UniformFloat`]: crate::distributions::uniform::UniformFloat -//! [`UniformDuration`]: crate::distributions::uniform::UniformDuration -//! [`SampleBorrow::borrow`]: crate::distributions::uniform::SampleBorrow::borrow - -use core::fmt; -use core::ops::{Range, RangeInclusive}; -use core::time::Duration; - -use crate::distributions::float::IntoFloat; -use crate::distributions::utils::{ - BoolAsSIMD, FloatAsSIMD, FloatSIMDUtils, IntAsSIMD, WideningMultiply, -}; -use crate::distributions::Distribution; -#[cfg(feature = "simd_support")] -use crate::distributions::Standard; -use crate::{Rng, RngCore}; - -#[cfg(feature = "simd_support")] -use core::simd::prelude::*; -#[cfg(feature = "simd_support")] -use core::simd::{LaneCount, SupportedLaneCount}; - -/// Error type returned from [`Uniform::new`] and `new_inclusive`. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Error { - /// `low > high`, or equal in case of exclusive range. - EmptyRange, - /// Input or range `high - low` is non-finite. Not relevant to integer types. - NonFinite, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Error::EmptyRange => "low > high (or equal if exclusive) in uniform distribution", - Error::NonFinite => "Non-finite range in uniform distribution", - }) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} - -#[cfg(feature = "serde1")] -use serde::{Deserialize, Serialize}; - -/// Sample values uniformly between two bounds. -/// -/// [`Uniform::new`] and [`Uniform::new_inclusive`] construct a uniform -/// distribution sampling from the given range; these functions may do extra -/// work up front to make sampling of multiple values faster. If only one sample -/// from the range is required, [`Rng::gen_range`] can be more efficient. -/// -/// When sampling from a constant range, many calculations can happen at -/// compile-time and all methods should be fast; for floating-point ranges and -/// the full range of integer types, this should have comparable performance to -/// the `Standard` distribution. -/// -/// Steps are taken to avoid bias, which might be present in naive -/// implementations; for example `rng.gen::() % 170` samples from the range -/// `[0, 169]` but is twice as likely to select numbers less than 85 than other -/// values. Further, the implementations here give more weight to the high-bits -/// generated by the RNG than the low bits, since with some RNGs the low-bits -/// are of lower quality than the high bits. -/// -/// Implementations must sample in `[low, high)` range for -/// `Uniform::new(low, high)`, i.e., excluding `high`. In particular, care must -/// be taken to ensure that rounding never results values `< low` or `>= high`. -/// -/// # Example -/// -/// ``` -/// use rand::distributions::{Distribution, Uniform}; -/// -/// let between = Uniform::try_from(10..10000).unwrap(); -/// let mut rng = rand::thread_rng(); -/// let mut sum = 0; -/// for _ in 0..1000 { -/// sum += between.sample(&mut rng); -/// } -/// println!("{}", sum); -/// ``` -/// -/// For a single sample, [`Rng::gen_range`] may be preferred: -/// -/// ``` -/// use rand::Rng; -/// -/// let mut rng = rand::thread_rng(); -/// println!("{}", rng.gen_range(0..10)); -/// ``` -/// -/// [`new`]: Uniform::new -/// [`new_inclusive`]: Uniform::new_inclusive -/// [`Rng::gen_range`]: Rng::gen_range -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] -#[cfg_attr( - feature = "serde1", - serde(bound(deserialize = "X::Sampler: Deserialize<'de>")) -)] -pub struct Uniform(X::Sampler); - -impl Uniform { - /// Create a new `Uniform` instance, which samples uniformly from the half - /// open range `[low, high)` (excluding `high`). - /// - /// For discrete types (e.g. integers), samples will always be strictly less - /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), - /// samples may equal `high` due to loss of precision but may not be - /// greater than `high`. - /// - /// Fails if `low >= high`, or if `low`, `high` or the range `high - low` is - /// non-finite. In release mode, only the range is checked. - pub fn new(low: B1, high: B2) -> Result, Error> - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - X::Sampler::new(low, high).map(Uniform) - } - - /// Create a new `Uniform` instance, which samples uniformly from the closed - /// range `[low, high]` (inclusive). - /// - /// Fails if `low > high`, or if `low`, `high` or the range `high - low` is - /// non-finite. In release mode, only the range is checked. - pub fn new_inclusive(low: B1, high: B2) -> Result, Error> - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - X::Sampler::new_inclusive(low, high).map(Uniform) - } -} - -impl Distribution for Uniform { - fn sample(&self, rng: &mut R) -> X { - self.0.sample(rng) - } -} - -/// Helper trait for creating objects using the correct implementation of -/// [`UniformSampler`] for the sampling type. -/// -/// See the [module documentation] on how to implement [`Uniform`] range -/// sampling for a custom type. -/// -/// [module documentation]: crate::distributions::uniform -pub trait SampleUniform: Sized { - /// The `UniformSampler` implementation supporting type `X`. - type Sampler: UniformSampler; -} - -/// Helper trait handling actual uniform sampling. -/// -/// See the [module documentation] on how to implement [`Uniform`] range -/// sampling for a custom type. -/// -/// Implementation of [`sample_single`] is optional, and is only useful when -/// the implementation can be faster than `Self::new(low, high).sample(rng)`. -/// -/// [module documentation]: crate::distributions::uniform -/// [`sample_single`]: UniformSampler::sample_single -pub trait UniformSampler: Sized { - /// The type sampled by this implementation. - type X; - - /// Construct self, with inclusive lower bound and exclusive upper bound `[low, high)`. - /// - /// For discrete types (e.g. integers), samples will always be strictly less - /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), - /// samples may equal `high` due to loss of precision but may not be - /// greater than `high`. - /// - /// Usually users should not call this directly but prefer to use - /// [`Uniform::new`]. - fn new(low: B1, high: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized; - - /// Construct self, with inclusive bounds `[low, high]`. - /// - /// Usually users should not call this directly but prefer to use - /// [`Uniform::new_inclusive`]. - fn new_inclusive(low: B1, high: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized; - - /// Sample a value. - fn sample(&self, rng: &mut R) -> Self::X; - - /// Sample a single value uniformly from a range with inclusive lower bound - /// and exclusive upper bound `[low, high)`. - /// - /// For discrete types (e.g. integers), samples will always be strictly less - /// than `high`. For (approximations of) continuous types (e.g. `f32`, `f64`), - /// samples may equal `high` due to loss of precision but may not be - /// greater than `high`. - /// - /// By default this is implemented using - /// `UniformSampler::new(low, high).sample(rng)`. However, for some types - /// more optimal implementations for single usage may be provided via this - /// method (which is the case for integers and floats). - /// Results may not be identical. - /// - /// Note that to use this method in a generic context, the type needs to be - /// retrieved via `SampleUniform::Sampler` as follows: - /// ``` - /// use rand::{thread_rng, distributions::uniform::{SampleUniform, UniformSampler}}; - /// # #[allow(unused)] - /// fn sample_from_range(lb: T, ub: T) -> T { - /// let mut rng = thread_rng(); - /// ::Sampler::sample_single(lb, ub, &mut rng).unwrap() - /// } - /// ``` - fn sample_single( - low: B1, - high: B2, - rng: &mut R, - ) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let uniform: Self = UniformSampler::new(low, high)?; - Ok(uniform.sample(rng)) - } - - /// Sample a single value uniformly from a range with inclusive lower bound - /// and inclusive upper bound `[low, high]`. - /// - /// By default this is implemented using - /// `UniformSampler::new_inclusive(low, high).sample(rng)`. However, for - /// some types more optimal implementations for single usage may be provided - /// via this method. - /// Results may not be identical. - fn sample_single_inclusive( - low: B1, - high: B2, - rng: &mut R, - ) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let uniform: Self = UniformSampler::new_inclusive(low, high)?; - Ok(uniform.sample(rng)) - } -} - -impl TryFrom> for Uniform { - type Error = Error; - - fn try_from(r: Range) -> Result, Error> { - Uniform::new(r.start, r.end) - } -} - -impl TryFrom> for Uniform { - type Error = Error; - - fn try_from(r: ::core::ops::RangeInclusive) -> Result, Error> { - Uniform::new_inclusive(r.start(), r.end()) - } -} - -/// Helper trait similar to [`Borrow`] but implemented -/// only for [`SampleUniform`] and references to [`SampleUniform`] -/// in order to resolve ambiguity issues. -/// -/// [`Borrow`]: std::borrow::Borrow -pub trait SampleBorrow { - /// Immutably borrows from an owned value. See [`Borrow::borrow`] - /// - /// [`Borrow::borrow`]: std::borrow::Borrow::borrow - fn borrow(&self) -> &Borrowed; -} -impl SampleBorrow for Borrowed -where - Borrowed: SampleUniform, -{ - #[inline(always)] - fn borrow(&self) -> &Borrowed { - self - } -} -impl<'a, Borrowed> SampleBorrow for &'a Borrowed -where - Borrowed: SampleUniform, -{ - #[inline(always)] - fn borrow(&self) -> &Borrowed { - self - } -} - -/// Range that supports generating a single sample efficiently. -/// -/// Any type implementing this trait can be used to specify the sampled range -/// for `Rng::gen_range`. -pub trait SampleRange { - /// Generate a sample from the given range. - fn sample_single(self, rng: &mut R) -> Result; - - /// Check whether the range is empty. - fn is_empty(&self) -> bool; -} - -impl SampleRange for Range { - #[inline] - fn sample_single(self, rng: &mut R) -> Result { - T::Sampler::sample_single(self.start, self.end, rng) - } - - #[inline] - fn is_empty(&self) -> bool { - !(self.start < self.end) - } -} - -impl SampleRange for RangeInclusive { - #[inline] - fn sample_single(self, rng: &mut R) -> Result { - T::Sampler::sample_single_inclusive(self.start(), self.end(), rng) - } - - #[inline] - fn is_empty(&self) -> bool { - !(self.start() <= self.end()) - } -} - -//////////////////////////////////////////////////////////////////////////////// - -// What follows are all back-ends. - -/// The back-end implementing [`UniformSampler`] for integer types. -/// -/// Unless you are implementing [`UniformSampler`] for your own type, this type -/// should not be used directly, use [`Uniform`] instead. -/// -/// # Implementation notes -/// -/// For simplicity, we use the same generic struct `UniformInt` for all -/// integer types `X`. This gives us only one field type, `X`; to store unsigned -/// values of this size, we take use the fact that these conversions are no-ops. -/// -/// For a closed range, the number of possible numbers we should generate is -/// `range = (high - low + 1)`. To avoid bias, we must ensure that the size of -/// our sample space, `zone`, is a multiple of `range`; other values must be -/// rejected (by replacing with a new random sample). -/// -/// As a special case, we use `range = 0` to represent the full range of the -/// result type (i.e. for `new_inclusive($ty::MIN, $ty::MAX)`). -/// -/// The optimum `zone` is the largest product of `range` which fits in our -/// (unsigned) target type. We calculate this by calculating how many numbers we -/// must reject: `reject = (MAX + 1) % range = (MAX - range + 1) % range`. Any (large) -/// product of `range` will suffice, thus in `sample_single` we multiply by a -/// power of 2 via bit-shifting (faster but may cause more rejections). -/// -/// The smallest integer PRNGs generate is `u32`. For 8- and 16-bit outputs we -/// use `u32` for our `zone` and samples (because it's not slower and because -/// it reduces the chance of having to reject a sample). In this case we cannot -/// store `zone` in the target type since it is too large, however we know -/// `ints_to_reject < range <= $uty::MAX`. -/// -/// An alternative to using a modulus is widening multiply: After a widening -/// multiply by `range`, the result is in the high word. Then comparing the low -/// word against `zone` makes sure our distribution is uniform. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct UniformInt { - low: X, - range: X, - thresh: X, // effectively 2.pow(max(64, uty_bits)) % range -} - -macro_rules! uniform_int_impl { - ($ty:ty, $uty:ty, $sample_ty:ident) => { - impl SampleUniform for $ty { - type Sampler = UniformInt<$ty>; - } - - impl UniformSampler for UniformInt<$ty> { - // We play free and fast with unsigned vs signed here - // (when $ty is signed), but that's fine, since the - // contract of this macro is for $ty and $uty to be - // "bit-equal", so casting between them is a no-op. - - type X = $ty; - - #[inline] // if the range is constant, this helps LLVM to do the - // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low < high) { - return Err(Error::EmptyRange); - } - UniformSampler::new_inclusive(low, high - 1) - } - - #[inline] // if the range is constant, this helps LLVM to do the - // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low <= high) { - return Err(Error::EmptyRange); - } - - let range = high.wrapping_sub(low).wrapping_add(1) as $uty; - let thresh = if range > 0 { - let range = $sample_ty::from(range); - (range.wrapping_neg() % range) - } else { - 0 - }; - - Ok(UniformInt { - low, - range: range as $ty, // type: $uty - thresh: thresh as $uty as $ty, // type: $sample_ty - }) - } - - /// Sample from distribution, Lemire's method, unbiased - #[inline] - fn sample(&self, rng: &mut R) -> Self::X { - let range = self.range as $uty as $sample_ty; - if range == 0 { - return rng.random(); - } - - let thresh = self.thresh as $uty as $sample_ty; - let hi = loop { - let (hi, lo) = rng.random::<$sample_ty>().wmul(range); - if lo >= thresh { - break hi; - } - }; - self.low.wrapping_add(hi as $ty) - } - - #[inline] - fn sample_single( - low_b: B1, - high_b: B2, - rng: &mut R, - ) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low < high) { - return Err(Error::EmptyRange); - } - Self::sample_single_inclusive(low, high - 1, rng) - } - - /// Sample single value, Canon's method, biased - /// - /// In the worst case, bias affects 1 in `2^n` samples where n is - /// 56 (`i8`), 48 (`i16`), 96 (`i32`), 64 (`i64`), 128 (`i128`). - #[cfg(not(feature = "unbiased"))] - #[inline] - fn sample_single_inclusive( - low_b: B1, - high_b: B2, - rng: &mut R, - ) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low <= high) { - return Err(Error::EmptyRange); - } - let range = high.wrapping_sub(low).wrapping_add(1) as $uty as $sample_ty; - if range == 0 { - // Range is MAX+1 (unrepresentable), so we need a special case - return Ok(rng.random()); - } - - // generate a sample using a sensible integer type - let (mut result, lo_order) = rng.random::<$sample_ty>().wmul(range); - - // if the sample is biased... - if lo_order > range.wrapping_neg() { - // ...generate a new sample to reduce bias... - let (new_hi_order, _) = (rng.random::<$sample_ty>()).wmul(range as $sample_ty); - // ... incrementing result on overflow - let is_overflow = lo_order.checked_add(new_hi_order as $sample_ty).is_none(); - result += is_overflow as $sample_ty; - } - - Ok(low.wrapping_add(result as $ty)) - } - - /// Sample single value, Canon's method, unbiased - #[cfg(feature = "unbiased")] - #[inline] - fn sample_single_inclusive( - low_b: B1, - high_b: B2, - rng: &mut R, - ) -> Result - where - B1: SampleBorrow<$ty> + Sized, - B2: SampleBorrow<$ty> + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low <= high) { - return Err(Error::EmptyRange); - } - let range = high.wrapping_sub(low).wrapping_add(1) as $uty as $sample_ty; - if range == 0 { - // Range is MAX+1 (unrepresentable), so we need a special case - return Ok(rng.random()); - } - - let (mut result, mut lo) = rng.random::<$sample_ty>().wmul(range); - - // In contrast to the biased sampler, we use a loop: - while lo > range.wrapping_neg() { - let (new_hi, new_lo) = (rng.random::<$sample_ty>()).wmul(range); - match lo.checked_add(new_hi) { - Some(x) if x < $sample_ty::MAX => { - // Anything less than MAX: last term is 0 - break; - } - None => { - // Overflow: last term is 1 - result += 1; - break; - } - _ => { - // Unlikely case: must check next sample - lo = new_lo; - continue; - } - } - } - - Ok(low.wrapping_add(result as $ty)) - } - } - }; -} - -uniform_int_impl! { i8, u8, u32 } -uniform_int_impl! { i16, u16, u32 } -uniform_int_impl! { i32, u32, u32 } -uniform_int_impl! { i64, u64, u64 } -uniform_int_impl! { i128, u128, u128 } -uniform_int_impl! { isize, usize, usize } -uniform_int_impl! { u8, u8, u32 } -uniform_int_impl! { u16, u16, u32 } -uniform_int_impl! { u32, u32, u32 } -uniform_int_impl! { u64, u64, u64 } -uniform_int_impl! { usize, usize, usize } -uniform_int_impl! { u128, u128, u128 } - -#[cfg(feature = "simd_support")] -macro_rules! uniform_simd_int_impl { - ($ty:ident, $unsigned:ident) => { - // The "pick the largest zone that can fit in an `u32`" optimization - // is less useful here. Multiple lanes complicate things, we don't - // know the PRNG's minimal output size, and casting to a larger vector - // is generally a bad idea for SIMD performance. The user can still - // implement it manually. - - #[cfg(feature = "simd_support")] - impl SampleUniform for Simd<$ty, LANES> - where - LaneCount: SupportedLaneCount, - Simd<$unsigned, LANES>: - WideningMultiply, Simd<$unsigned, LANES>)>, - Standard: Distribution>, - { - type Sampler = UniformInt>; - } - - #[cfg(feature = "simd_support")] - impl UniformSampler for UniformInt> - where - LaneCount: SupportedLaneCount, - Simd<$unsigned, LANES>: - WideningMultiply, Simd<$unsigned, LANES>)>, - Standard: Distribution>, - { - type X = Simd<$ty, LANES>; - - #[inline] // if the range is constant, this helps LLVM to do the - // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Result - where B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low.simd_lt(high).all()) { - return Err(Error::EmptyRange); - } - UniformSampler::new_inclusive(low, high - Simd::splat(1)) - } - - #[inline] // if the range is constant, this helps LLVM to do the - // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Result - where B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low.simd_le(high).all()) { - return Err(Error::EmptyRange); - } - - // NOTE: all `Simd` operations are inherently wrapping, - // see https://doc.rust-lang.org/std/simd/struct.Simd.html - let range: Simd<$unsigned, LANES> = ((high - low) + Simd::splat(1)).cast(); - - // We must avoid divide-by-zero by using 0 % 1 == 0. - let not_full_range = range.simd_gt(Simd::splat(0)); - let modulo = not_full_range.select(range, Simd::splat(1)); - let ints_to_reject = range.wrapping_neg() % modulo; - - Ok(UniformInt { - low, - // These are really $unsigned values, but store as $ty: - range: range.cast(), - thresh: ints_to_reject.cast(), - }) - } - - fn sample(&self, rng: &mut R) -> Self::X { - let range: Simd<$unsigned, LANES> = self.range.cast(); - let thresh: Simd<$unsigned, LANES> = self.thresh.cast(); - - // This might seem very slow, generating a whole new - // SIMD vector for every sample rejection. For most uses - // though, the chance of rejection is small and provides good - // general performance. With multiple lanes, that chance is - // multiplied. To mitigate this, we replace only the lanes of - // the vector which fail, iteratively reducing the chance of - // rejection. The replacement method does however add a little - // overhead. Benchmarking or calculating probabilities might - // reveal contexts where this replacement method is slower. - let mut v: Simd<$unsigned, LANES> = rng.random(); - loop { - let (hi, lo) = v.wmul(range); - let mask = lo.simd_ge(thresh); - if mask.all() { - let hi: Simd<$ty, LANES> = hi.cast(); - // wrapping addition - let result = self.low + hi; - // `select` here compiles to a blend operation - // When `range.eq(0).none()` the compare and blend - // operations are avoided. - let v: Simd<$ty, LANES> = v.cast(); - return range.simd_gt(Simd::splat(0)).select(result, v); - } - // Replace only the failing lanes - v = mask.select(v, rng.random()); - } - } - } - }; - - // bulk implementation - ($(($unsigned:ident, $signed:ident)),+) => { - $( - uniform_simd_int_impl!($unsigned, $unsigned); - uniform_simd_int_impl!($signed, $unsigned); - )+ - }; -} - -#[cfg(feature = "simd_support")] -uniform_simd_int_impl! { (u8, i8), (u16, i16), (u32, i32), (u64, i64) } - -impl SampleUniform for char { - type Sampler = UniformChar; -} - -/// The back-end implementing [`UniformSampler`] for `char`. -/// -/// Unless you are implementing [`UniformSampler`] for your own type, this type -/// should not be used directly, use [`Uniform`] instead. -/// -/// This differs from integer range sampling since the range `0xD800..=0xDFFF` -/// are used for surrogate pairs in UCS and UTF-16, and consequently are not -/// valid Unicode code points. We must therefore avoid sampling values in this -/// range. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct UniformChar { - sampler: UniformInt, -} - -/// UTF-16 surrogate range start -const CHAR_SURROGATE_START: u32 = 0xD800; -/// UTF-16 surrogate range size -const CHAR_SURROGATE_LEN: u32 = 0xE000 - CHAR_SURROGATE_START; - -/// Convert `char` to compressed `u32` -fn char_to_comp_u32(c: char) -> u32 { - match c as u32 { - c if c >= CHAR_SURROGATE_START => c - CHAR_SURROGATE_LEN, - c => c, - } -} - -impl UniformSampler for UniformChar { - type X = char; - - #[inline] // if the range is constant, this helps LLVM to do the - // calculations at compile-time. - fn new(low_b: B1, high_b: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = char_to_comp_u32(*low_b.borrow()); - let high = char_to_comp_u32(*high_b.borrow()); - let sampler = UniformInt::::new(low, high); - sampler.map(|sampler| UniformChar { sampler }) - } - - #[inline] // if the range is constant, this helps LLVM to do the - // calculations at compile-time. - fn new_inclusive(low_b: B1, high_b: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = char_to_comp_u32(*low_b.borrow()); - let high = char_to_comp_u32(*high_b.borrow()); - let sampler = UniformInt::::new_inclusive(low, high); - sampler.map(|sampler| UniformChar { sampler }) - } - - fn sample(&self, rng: &mut R) -> Self::X { - let mut x = self.sampler.sample(rng); - if x >= CHAR_SURROGATE_START { - x += CHAR_SURROGATE_LEN; - } - // SAFETY: x must not be in surrogate range or greater than char::MAX. - // This relies on range constructors which accept char arguments. - // Validity of input char values is assumed. - unsafe { core::char::from_u32_unchecked(x) } - } -} - -/// Note: the `String` is potentially left with excess capacity if the range -/// includes non ascii chars; optionally the user may call -/// `string.shrink_to_fit()` afterwards. -#[cfg(feature = "alloc")] -impl super::DistString for Uniform { - fn append_string( - &self, - rng: &mut R, - string: &mut alloc::string::String, - len: usize, - ) { - // Getting the hi value to assume the required length to reserve in string. - let mut hi = self.0.sampler.low + self.0.sampler.range - 1; - if hi >= CHAR_SURROGATE_START { - hi += CHAR_SURROGATE_LEN; - } - // Get the utf8 length of hi to minimize extra space. - let max_char_len = char::from_u32(hi).map(char::len_utf8).unwrap_or(4); - string.reserve(max_char_len * len); - string.extend(self.sample_iter(rng).take(len)) - } -} - -/// The back-end implementing [`UniformSampler`] for floating-point types. -/// -/// Unless you are implementing [`UniformSampler`] for your own type, this type -/// should not be used directly, use [`Uniform`] instead. -/// -/// # Implementation notes -/// -/// Instead of generating a float in the `[0, 1)` range using [`Standard`], the -/// `UniformFloat` implementation converts the output of an PRNG itself. This -/// way one or two steps can be optimized out. -/// -/// The floats are first converted to a value in the `[1, 2)` interval using a -/// transmute-based method, and then mapped to the expected range with a -/// multiply and addition. Values produced this way have what equals 23 bits of -/// random digits for an `f32`, and 52 for an `f64`. -/// -/// [`new`]: UniformSampler::new -/// [`new_inclusive`]: UniformSampler::new_inclusive -/// [`Standard`]: crate::distributions::Standard -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct UniformFloat { - low: X, - scale: X, -} - -macro_rules! uniform_float_impl { - ($($meta:meta)?, $ty:ty, $uty:ident, $f_scalar:ident, $u_scalar:ident, $bits_to_discard:expr) => { - $(#[cfg($meta)])? - impl UniformFloat<$ty> { - /// Construct, reducing `scale` as required to ensure that rounding - /// can never yield values greater than `high`. - /// - /// Note: though it may be tempting to use a variant of this method - /// to ensure that samples from `[low, high)` are always strictly - /// less than `high`, this approach may be very slow where - /// `scale.abs()` is much smaller than `high.abs()` - /// (example: `low=0.99999999997819644, high=1.`). - fn new_bounded(low: $ty, high: $ty, mut scale: $ty) -> Self { - let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); - - loop { - let mask = (scale * max_rand + low).gt_mask(high); - if !mask.any() { - break; - } - scale = scale.decrease_masked(mask); - } - - debug_assert!(<$ty>::splat(0.0).all_le(scale)); - - UniformFloat { low, scale } - } - } - - $(#[cfg($meta)])? - impl SampleUniform for $ty { - type Sampler = UniformFloat<$ty>; - } - - $(#[cfg($meta)])? - impl UniformSampler for UniformFloat<$ty> { - type X = $ty; - - fn new(low_b: B1, high_b: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - #[cfg(debug_assertions)] - if !(low.all_finite()) || !(high.all_finite()) { - return Err(Error::NonFinite); - } - if !(low.all_lt(high)) { - return Err(Error::EmptyRange); - } - - let scale = high - low; - if !(scale.all_finite()) { - return Err(Error::NonFinite); - } - - Ok(Self::new_bounded(low, high, scale)) - } - - fn new_inclusive(low_b: B1, high_b: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - #[cfg(debug_assertions)] - if !(low.all_finite()) || !(high.all_finite()) { - return Err(Error::NonFinite); - } - if !low.all_le(high) { - return Err(Error::EmptyRange); - } - - let max_rand = <$ty>::splat(1.0 as $f_scalar - $f_scalar::EPSILON); - let scale = (high - low) / max_rand; - if !scale.all_finite() { - return Err(Error::NonFinite); - } - - Ok(Self::new_bounded(low, high, scale)) - } - - fn sample(&self, rng: &mut R) -> Self::X { - // Generate a value in the range [1, 2) - let value1_2 = (rng.random::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); - - // Get a value in the range [0, 1) to avoid overflow when multiplying by scale - let value0_1 = value1_2 - <$ty>::splat(1.0); - - // We don't use `f64::mul_add`, because it is not available with - // `no_std`. Furthermore, it is slower for some targets (but - // faster for others). However, the order of multiplication and - // addition is important, because on some platforms (e.g. ARM) - // it will be optimized to a single (non-FMA) instruction. - value0_1 * self.scale + self.low - } - - #[inline] - fn sample_single(low_b: B1, high_b: B2, rng: &mut R) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - Self::sample_single_inclusive(low_b, high_b, rng) - } - - #[inline] - fn sample_single_inclusive(low_b: B1, high_b: B2, rng: &mut R) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - #[cfg(debug_assertions)] - if !low.all_finite() || !high.all_finite() { - return Err(Error::NonFinite); - } - if !low.all_le(high) { - return Err(Error::EmptyRange); - } - let scale = high - low; - if !scale.all_finite() { - return Err(Error::NonFinite); - } - - // Generate a value in the range [1, 2) - let value1_2 = - (rng.random::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0); - - // Get a value in the range [0, 1) to avoid overflow when multiplying by scale - let value0_1 = value1_2 - <$ty>::splat(1.0); - - // Doing multiply before addition allows some architectures - // to use a single instruction. - Ok(value0_1 * scale + low) - } - } - }; -} - -uniform_float_impl! { , f32, u32, f32, u32, 32 - 23 } -uniform_float_impl! { , f64, u64, f64, u64, 64 - 52 } - -#[cfg(feature = "simd_support")] -uniform_float_impl! { feature = "simd_support", f32x2, u32x2, f32, u32, 32 - 23 } -#[cfg(feature = "simd_support")] -uniform_float_impl! { feature = "simd_support", f32x4, u32x4, f32, u32, 32 - 23 } -#[cfg(feature = "simd_support")] -uniform_float_impl! { feature = "simd_support", f32x8, u32x8, f32, u32, 32 - 23 } -#[cfg(feature = "simd_support")] -uniform_float_impl! { feature = "simd_support", f32x16, u32x16, f32, u32, 32 - 23 } - -#[cfg(feature = "simd_support")] -uniform_float_impl! { feature = "simd_support", f64x2, u64x2, f64, u64, 64 - 52 } -#[cfg(feature = "simd_support")] -uniform_float_impl! { feature = "simd_support", f64x4, u64x4, f64, u64, 64 - 52 } -#[cfg(feature = "simd_support")] -uniform_float_impl! { feature = "simd_support", f64x8, u64x8, f64, u64, 64 - 52 } - -/// The back-end implementing [`UniformSampler`] for `Duration`. -/// -/// Unless you are implementing [`UniformSampler`] for your own types, this type -/// should not be used directly, use [`Uniform`] instead. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -pub struct UniformDuration { - mode: UniformDurationMode, - offset: u32, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -enum UniformDurationMode { - Small { - secs: u64, - nanos: Uniform, - }, - Medium { - nanos: Uniform, - }, - Large { - max_secs: u64, - max_nanos: u32, - secs: Uniform, - }, -} - -impl SampleUniform for Duration { - type Sampler = UniformDuration; -} - -impl UniformSampler for UniformDuration { - type X = Duration; - - #[inline] - fn new(low_b: B1, high_b: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low < high) { - return Err(Error::EmptyRange); - } - UniformDuration::new_inclusive(low, high - Duration::new(0, 1)) - } - - #[inline] - fn new_inclusive(low_b: B1, high_b: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = *low_b.borrow(); - let high = *high_b.borrow(); - if !(low <= high) { - return Err(Error::EmptyRange); - } - - let low_s = low.as_secs(); - let low_n = low.subsec_nanos(); - let mut high_s = high.as_secs(); - let mut high_n = high.subsec_nanos(); - - if high_n < low_n { - high_s -= 1; - high_n += 1_000_000_000; - } - - let mode = if low_s == high_s { - UniformDurationMode::Small { - secs: low_s, - nanos: Uniform::new_inclusive(low_n, high_n)?, - } - } else { - let max = high_s - .checked_mul(1_000_000_000) - .and_then(|n| n.checked_add(u64::from(high_n))); - - if let Some(higher_bound) = max { - let lower_bound = low_s * 1_000_000_000 + u64::from(low_n); - UniformDurationMode::Medium { - nanos: Uniform::new_inclusive(lower_bound, higher_bound)?, - } - } else { - // An offset is applied to simplify generation of nanoseconds - let max_nanos = high_n - low_n; - UniformDurationMode::Large { - max_secs: high_s, - max_nanos, - secs: Uniform::new_inclusive(low_s, high_s)?, - } - } - }; - Ok(UniformDuration { - mode, - offset: low_n, - }) - } - - #[inline] - fn sample(&self, rng: &mut R) -> Duration { - match self.mode { - UniformDurationMode::Small { secs, nanos } => { - let n = nanos.sample(rng); - Duration::new(secs, n) - } - UniformDurationMode::Medium { nanos } => { - let nanos = nanos.sample(rng); - Duration::new(nanos / 1_000_000_000, (nanos % 1_000_000_000) as u32) - } - UniformDurationMode::Large { - max_secs, - max_nanos, - secs, - } => { - // constant folding means this is at least as fast as `Rng::sample(Range)` - let nano_range = Uniform::new(0, 1_000_000_000).unwrap(); - loop { - let s = secs.sample(rng); - let n = nano_range.sample(rng); - if !(s == max_secs && n > max_nanos) { - let sum = n + self.offset; - break Duration::new(s, sum); - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::distributions::utils::FloatSIMDScalarUtils; - use crate::rngs::mock::StepRng; - - #[test] - #[cfg(feature = "serde1")] - fn test_serialization_uniform_duration() { - let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)).unwrap(); - let de_distr: UniformDuration = - bincode::deserialize(&bincode::serialize(&distr).unwrap()).unwrap(); - assert_eq!(distr, de_distr); - } - - #[test] - #[cfg(feature = "serde1")] - fn test_uniform_serialization() { - let unit_box: Uniform = Uniform::new(-1, 1).unwrap(); - let de_unit_box: Uniform = - bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); - assert_eq!(unit_box.0, de_unit_box.0); - - let unit_box: Uniform = Uniform::new(-1., 1.).unwrap(); - let de_unit_box: Uniform = - bincode::deserialize(&bincode::serialize(&unit_box).unwrap()).unwrap(); - assert_eq!(unit_box.0, de_unit_box.0); - } - - #[test] - fn test_uniform_bad_limits_equal_int() { - assert_eq!(Uniform::new(10, 10), Err(Error::EmptyRange)); - } - - #[test] - fn test_uniform_good_limits_equal_int() { - let mut rng = crate::test::rng(804); - let dist = Uniform::new_inclusive(10, 10).unwrap(); - for _ in 0..20 { - assert_eq!(rng.sample(dist), 10); - } - } - - #[test] - fn test_uniform_bad_limits_flipped_int() { - assert_eq!(Uniform::new(10, 5), Err(Error::EmptyRange)); - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_integers() { - let mut rng = crate::test::rng(251); - macro_rules! t { - ($ty:ident, $v:expr, $le:expr, $lt:expr) => {{ - for &(low, high) in $v.iter() { - let my_uniform = Uniform::new(low, high).unwrap(); - for _ in 0..1000 { - let v: $ty = rng.sample(my_uniform); - assert!($le(low, v) && $lt(v, high)); - } - - let my_uniform = Uniform::new_inclusive(low, high).unwrap(); - for _ in 0..1000 { - let v: $ty = rng.sample(my_uniform); - assert!($le(low, v) && $le(v, high)); - } - - let my_uniform = Uniform::new(&low, high).unwrap(); - for _ in 0..1000 { - let v: $ty = rng.sample(my_uniform); - assert!($le(low, v) && $lt(v, high)); - } - - let my_uniform = Uniform::new_inclusive(&low, &high).unwrap(); - for _ in 0..1000 { - let v: $ty = rng.sample(my_uniform); - assert!($le(low, v) && $le(v, high)); - } - - for _ in 0..1000 { - let v = <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng).unwrap(); - assert!($le(low, v) && $lt(v, high)); - } - - for _ in 0..1000 { - let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive(low, high, &mut rng).unwrap(); - assert!($le(low, v) && $le(v, high)); - } - } - }}; - - // scalar bulk - ($($ty:ident),*) => {{ - $(t!( - $ty, - [(0, 10), (10, 127), ($ty::MIN, $ty::MAX)], - |x, y| x <= y, - |x, y| x < y - );)* - }}; - - // simd bulk - ($($ty:ident),* => $scalar:ident) => {{ - $(t!( - $ty, - [ - ($ty::splat(0), $ty::splat(10)), - ($ty::splat(10), $ty::splat(127)), - ($ty::splat($scalar::MIN), $ty::splat($scalar::MAX)), - ], - |x: $ty, y| x.simd_le(y).all(), - |x: $ty, y| x.simd_lt(y).all() - );)* - }}; - } - t!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, i128, u128); - - #[cfg(feature = "simd_support")] - { - t!(u8x4, u8x8, u8x16, u8x32, u8x64 => u8); - t!(i8x4, i8x8, i8x16, i8x32, i8x64 => i8); - t!(u16x2, u16x4, u16x8, u16x16, u16x32 => u16); - t!(i16x2, i16x4, i16x8, i16x16, i16x32 => i16); - t!(u32x2, u32x4, u32x8, u32x16 => u32); - t!(i32x2, i32x4, i32x8, i32x16 => i32); - t!(u64x2, u64x4, u64x8 => u64); - t!(i64x2, i64x4, i64x8 => i64); - } - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_char() { - let mut rng = crate::test::rng(891); - let mut max = core::char::from_u32(0).unwrap(); - for _ in 0..100 { - let c = rng.gen_range('A'..='Z'); - assert!(c.is_ascii_uppercase()); - max = max.max(c); - } - assert_eq!(max, 'Z'); - let d = Uniform::new( - core::char::from_u32(0xD7F0).unwrap(), - core::char::from_u32(0xE010).unwrap(), - ) - .unwrap(); - for _ in 0..100 { - let c = d.sample(&mut rng); - assert!((c as u32) < 0xD800 || (c as u32) > 0xDFFF); - } - #[cfg(feature = "alloc")] - { - use crate::distributions::DistString; - let string1 = d.sample_string(&mut rng, 100); - assert_eq!(string1.capacity(), 300); - let string2 = Uniform::new( - core::char::from_u32(0x0000).unwrap(), - core::char::from_u32(0x0080).unwrap(), - ) - .unwrap() - .sample_string(&mut rng, 100); - assert_eq!(string2.capacity(), 100); - let string3 = Uniform::new_inclusive( - core::char::from_u32(0x0000).unwrap(), - core::char::from_u32(0x0080).unwrap(), - ) - .unwrap() - .sample_string(&mut rng, 100); - assert_eq!(string3.capacity(), 200); - } - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_floats() { - let mut rng = crate::test::rng(252); - let mut zero_rng = StepRng::new(0, 0); - let mut max_rng = StepRng::new(0xffff_ffff_ffff_ffff, 0); - macro_rules! t { - ($ty:ty, $f_scalar:ident, $bits_shifted:expr) => {{ - let v: &[($f_scalar, $f_scalar)] = &[ - (0.0, 100.0), - (-1e35, -1e25), - (1e-35, 1e-25), - (-1e35, 1e35), - (<$f_scalar>::from_bits(0), <$f_scalar>::from_bits(3)), - (-<$f_scalar>::from_bits(10), -<$f_scalar>::from_bits(1)), - (-<$f_scalar>::from_bits(5), 0.0), - (-<$f_scalar>::from_bits(7), -0.0), - (0.1 * $f_scalar::MAX, $f_scalar::MAX), - (-$f_scalar::MAX * 0.2, $f_scalar::MAX * 0.7), - ]; - for &(low_scalar, high_scalar) in v.iter() { - for lane in 0..<$ty>::LEN { - let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); - let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); - let my_uniform = Uniform::new(low, high).unwrap(); - let my_incl_uniform = Uniform::new_inclusive(low, high).unwrap(); - for _ in 0..100 { - let v = rng.sample(my_uniform).extract(lane); - assert!(low_scalar <= v && v <= high_scalar); - let v = rng.sample(my_incl_uniform).extract(lane); - assert!(low_scalar <= v && v <= high_scalar); - let v = - <$ty as SampleUniform>::Sampler::sample_single(low, high, &mut rng) - .unwrap() - .extract(lane); - assert!(low_scalar <= v && v <= high_scalar); - let v = <$ty as SampleUniform>::Sampler::sample_single_inclusive( - low, high, &mut rng, - ) - .unwrap() - .extract(lane); - assert!(low_scalar <= v && v <= high_scalar); - } - - assert_eq!( - rng.sample(Uniform::new_inclusive(low, low).unwrap()) - .extract(lane), - low_scalar - ); - - assert_eq!(zero_rng.sample(my_uniform).extract(lane), low_scalar); - assert_eq!(zero_rng.sample(my_incl_uniform).extract(lane), low_scalar); - assert_eq!( - <$ty as SampleUniform>::Sampler::sample_single( - low, - high, - &mut zero_rng - ) - .unwrap() - .extract(lane), - low_scalar - ); - assert_eq!( - <$ty as SampleUniform>::Sampler::sample_single_inclusive( - low, - high, - &mut zero_rng - ) - .unwrap() - .extract(lane), - low_scalar - ); - - assert!(max_rng.sample(my_uniform).extract(lane) <= high_scalar); - assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar); - // sample_single cannot cope with max_rng: - // assert!(<$ty as SampleUniform>::Sampler - // ::sample_single(low, high, &mut max_rng).unwrap() - // .extract(lane) <= high_scalar); - assert!( - <$ty as SampleUniform>::Sampler::sample_single_inclusive( - low, - high, - &mut max_rng - ) - .unwrap() - .extract(lane) - <= high_scalar - ); - - // Don't run this test for really tiny differences between high and low - // since for those rounding might result in selecting high for a very - // long time. - if (high_scalar - low_scalar) > 0.0001 { - let mut lowering_max_rng = StepRng::new( - 0xffff_ffff_ffff_ffff, - (-1i64 << $bits_shifted) as u64, - ); - assert!( - <$ty as SampleUniform>::Sampler::sample_single( - low, - high, - &mut lowering_max_rng - ) - .unwrap() - .extract(lane) - <= high_scalar - ); - } - } - } - - assert_eq!( - rng.sample(Uniform::new_inclusive($f_scalar::MAX, $f_scalar::MAX).unwrap()), - $f_scalar::MAX - ); - assert_eq!( - rng.sample(Uniform::new_inclusive(-$f_scalar::MAX, -$f_scalar::MAX).unwrap()), - -$f_scalar::MAX - ); - }}; - } - - t!(f32, f32, 32 - 23); - t!(f64, f64, 64 - 52); - #[cfg(feature = "simd_support")] - { - t!(f32x2, f32, 32 - 23); - t!(f32x4, f32, 32 - 23); - t!(f32x8, f32, 32 - 23); - t!(f32x16, f32, 32 - 23); - t!(f64x2, f64, 64 - 52); - t!(f64x4, f64, 64 - 52); - t!(f64x8, f64, 64 - 52); - } - } - - #[test] - fn test_float_overflow() { - assert_eq!(Uniform::try_from(f64::MIN..f64::MAX), Err(Error::NonFinite)); - } - - #[test] - #[should_panic] - fn test_float_overflow_single() { - let mut rng = crate::test::rng(252); - rng.gen_range(f64::MIN..f64::MAX); - } - - #[test] - #[cfg(all(feature = "std", panic = "unwind"))] - fn test_float_assertions() { - use super::SampleUniform; - fn range(low: T, high: T) -> Result { - let mut rng = crate::test::rng(253); - T::Sampler::sample_single(low, high, &mut rng) - } - - macro_rules! t { - ($ty:ident, $f_scalar:ident) => {{ - let v: &[($f_scalar, $f_scalar)] = &[ - ($f_scalar::NAN, 0.0), - (1.0, $f_scalar::NAN), - ($f_scalar::NAN, $f_scalar::NAN), - (1.0, 0.5), - ($f_scalar::MAX, -$f_scalar::MAX), - ($f_scalar::INFINITY, $f_scalar::INFINITY), - ($f_scalar::NEG_INFINITY, $f_scalar::NEG_INFINITY), - ($f_scalar::NEG_INFINITY, 5.0), - (5.0, $f_scalar::INFINITY), - ($f_scalar::NAN, $f_scalar::INFINITY), - ($f_scalar::NEG_INFINITY, $f_scalar::NAN), - ($f_scalar::NEG_INFINITY, $f_scalar::INFINITY), - ]; - for &(low_scalar, high_scalar) in v.iter() { - for lane in 0..<$ty>::LEN { - let low = <$ty>::splat(0.0 as $f_scalar).replace(lane, low_scalar); - let high = <$ty>::splat(1.0 as $f_scalar).replace(lane, high_scalar); - assert!(range(low, high).is_err()); - assert!(Uniform::new(low, high).is_err()); - assert!(Uniform::new_inclusive(low, high).is_err()); - assert!(Uniform::new(low, low).is_err()); - } - } - }}; - } - - t!(f32, f32); - t!(f64, f64); - #[cfg(feature = "simd_support")] - { - t!(f32x2, f32); - t!(f32x4, f32); - t!(f32x8, f32); - t!(f32x16, f32); - t!(f64x2, f64); - t!(f64x4, f64); - t!(f64x8, f64); - } - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_durations() { - let mut rng = crate::test::rng(253); - - let v = &[ - (Duration::new(10, 50000), Duration::new(100, 1234)), - (Duration::new(0, 100), Duration::new(1, 50)), - (Duration::new(0, 0), Duration::new(u64::MAX, 999_999_999)), - ]; - for &(low, high) in v.iter() { - let my_uniform = Uniform::new(low, high).unwrap(); - for _ in 0..1000 { - let v = rng.sample(my_uniform); - assert!(low <= v && v < high); - } - } - } - - #[test] - fn test_custom_uniform() { - use crate::distributions::uniform::{ - SampleBorrow, SampleUniform, UniformFloat, UniformSampler, - }; - #[derive(Clone, Copy, PartialEq, PartialOrd)] - struct MyF32 { - x: f32, - } - #[derive(Clone, Copy, Debug)] - struct UniformMyF32(UniformFloat); - impl UniformSampler for UniformMyF32 { - type X = MyF32; - - fn new(low: B1, high: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - UniformFloat::::new(low.borrow().x, high.borrow().x).map(UniformMyF32) - } - - fn new_inclusive(low: B1, high: B2) -> Result - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - UniformSampler::new(low, high) - } - - fn sample(&self, rng: &mut R) -> Self::X { - MyF32 { - x: self.0.sample(rng), - } - } - } - impl SampleUniform for MyF32 { - type Sampler = UniformMyF32; - } - - let (low, high) = (MyF32 { x: 17.0f32 }, MyF32 { x: 22.0f32 }); - let uniform = Uniform::new(low, high).unwrap(); - let mut rng = crate::test::rng(804); - for _ in 0..100 { - let x: MyF32 = rng.sample(uniform); - assert!(low <= x && x < high); - } - } - - #[test] - fn test_uniform_from_std_range() { - let r = Uniform::try_from(2u32..7).unwrap(); - assert_eq!(r.0.low, 2); - assert_eq!(r.0.range, 5); - let r = Uniform::try_from(2.0f64..7.0).unwrap(); - assert_eq!(r.0.low, 2.0); - assert_eq!(r.0.scale, 5.0); - } - - #[test] - fn test_uniform_from_std_range_bad_limits() { - #![allow(clippy::reversed_empty_ranges)] - assert!(Uniform::try_from(100..10).is_err()); - assert!(Uniform::try_from(100..100).is_err()); - assert!(Uniform::try_from(100.0..10.0).is_err()); - assert!(Uniform::try_from(100.0..100.0).is_err()); - } - - #[test] - fn test_uniform_from_std_range_inclusive() { - let r = Uniform::try_from(2u32..=6).unwrap(); - assert_eq!(r.0.low, 2); - assert_eq!(r.0.range, 5); - let r = Uniform::try_from(2.0f64..=7.0).unwrap(); - assert_eq!(r.0.low, 2.0); - assert!(r.0.scale > 5.0); - assert!(r.0.scale < 5.0 + 1e-14); - } - - #[test] - fn test_uniform_from_std_range_inclusive_bad_limits() { - #![allow(clippy::reversed_empty_ranges)] - assert!(Uniform::try_from(100..=10).is_err()); - assert!(Uniform::try_from(100..=99).is_err()); - assert!(Uniform::try_from(100.0..=10.0).is_err()); - assert!(Uniform::try_from(100.0..=99.0).is_err()); - } - - #[test] - fn value_stability() { - fn test_samples( - lb: T, - ub: T, - expected_single: &[T], - expected_multiple: &[T], - ) where - Uniform: Distribution, - { - let mut rng = crate::test::rng(897); - let mut buf = [lb; 3]; - - for x in &mut buf { - *x = T::Sampler::sample_single(lb, ub, &mut rng).unwrap(); - } - assert_eq!(&buf, expected_single); - - let distr = Uniform::new(lb, ub).unwrap(); - for x in &mut buf { - *x = rng.sample(&distr); - } - assert_eq!(&buf, expected_multiple); - } - - // We test on a sub-set of types; possibly we should do more. - // TODO: SIMD types - - test_samples(11u8, 219, &[17, 66, 214], &[181, 93, 165]); - test_samples(11u32, 219, &[17, 66, 214], &[181, 93, 165]); - - test_samples( - 0f32, - 1e-2f32, - &[0.0003070104, 0.0026630748, 0.00979833], - &[0.008194133, 0.00398172, 0.007428536], - ); - test_samples( - -1e10f64, - 1e10f64, - &[-4673848682.871551, 6388267422.932352, 4857075081.198343], - &[1173375212.1808167, 1917642852.109581, 2365076174.3153973], - ); - - test_samples( - Duration::new(2, 0), - Duration::new(4, 0), - &[ - Duration::new(2, 532615131), - Duration::new(3, 638826742), - Duration::new(3, 485707508), - ], - &[ - Duration::new(3, 117337521), - Duration::new(3, 191764285), - Duration::new(3, 236507617), - ], - ); - } - - #[test] - fn uniform_distributions_can_be_compared() { - assert_eq!( - Uniform::new(1.0, 2.0).unwrap(), - Uniform::new(1.0, 2.0).unwrap() - ); - - // To cover UniformInt - assert_eq!( - Uniform::new(1_u32, 2_u32).unwrap(), - Uniform::new(1_u32, 2_u32).unwrap() - ); - } -} diff --git a/src/lib.rs b/src/lib.rs index 8b43f0320f9..ce0206db09b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ //! To get you started quickly, the easiest and highest-level way to get //! a random value is to use [`random()`]; alternatively you can use //! [`thread_rng()`]. The [`Rng`] trait provides a useful API on all RNGs, while -//! the [`distributions`] and [`seq`] modules provide further +//! the [`distr`] and [`seq`] modules provide further //! functionality on top of RNGs. //! //! ``` @@ -97,7 +97,7 @@ macro_rules! error { ($($x:tt)*) => ( pub use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; // Public modules -pub mod distributions; +pub mod distr; pub mod prelude; mod rng; pub mod rngs; @@ -109,7 +109,7 @@ pub use crate::rngs::thread::thread_rng; pub use rng::{Fill, Rng}; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] -use crate::distributions::{Distribution, Standard}; +use crate::distr::{Distribution, Standard}; /// Generates a random value using the thread-local random number generator. /// @@ -153,7 +153,7 @@ use crate::distributions::{Distribution, Standard}; /// } /// ``` /// -/// [`Standard`]: distributions::Standard +/// [`Standard`]: distr::Standard /// [`ThreadRng`]: rngs::ThreadRng #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] #[inline] diff --git a/src/prelude.rs b/src/prelude.rs index 2605bca91f4..37b703742df 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -19,7 +19,7 @@ //! ``` #[doc(no_inline)] -pub use crate::distributions::Distribution; +pub use crate::distr::Distribution; #[cfg(feature = "small_rng")] #[doc(no_inline)] pub use crate::rngs::SmallRng; diff --git a/src/rng.rs b/src/rng.rs index 06fc2bb741e..9c015eddd41 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -9,8 +9,8 @@ //! [`Rng`] trait -use crate::distributions::uniform::{SampleRange, SampleUniform}; -use crate::distributions::{self, Distribution, Standard}; +use crate::distr::uniform::{SampleRange, SampleUniform}; +use crate::distr::{self, Distribution, Standard}; use core::num::Wrapping; use core::{mem, slice}; use rand_core::RngCore; @@ -86,7 +86,7 @@ pub trait Rng: RngCore { /// rng.fill(&mut arr2); // array fill /// ``` /// - /// [`Standard`]: distributions::Standard + /// [`Standard`]: distr::Standard #[inline] fn random(&mut self) -> T where @@ -125,7 +125,7 @@ pub trait Rng: RngCore { /// println!("{}", n); /// ``` /// - /// [`Uniform`]: distributions::uniform::Uniform + /// [`Uniform`]: distr::uniform::Uniform #[track_caller] fn gen_range(&mut self, range: R) -> T where @@ -139,7 +139,7 @@ pub trait Rng: RngCore { /// Generate values via an iterator /// /// This is a just a wrapper over [`Rng::sample_iter`] using - /// [`distributions::Standard`]. + /// [`distr::Standard`]. /// /// Note: this method consumes its argument. Use /// `(&mut rng).gen_iter()` to avoid consuming the RNG. @@ -154,7 +154,7 @@ pub trait Rng: RngCore { /// assert_eq!(&v, &[1, 2, 3, 4, 5]); /// ``` #[inline] - fn gen_iter(self) -> distributions::DistIter + fn gen_iter(self) -> distr::DistIter where Self: Sized, Standard: Distribution, @@ -168,7 +168,7 @@ pub trait Rng: RngCore { /// /// ``` /// use rand::{thread_rng, Rng}; - /// use rand::distributions::Uniform; + /// use rand::distr::Uniform; /// /// let mut rng = thread_rng(); /// let x = rng.sample(Uniform::new(10u32, 15).unwrap()); @@ -189,7 +189,7 @@ pub trait Rng: RngCore { /// /// ``` /// use rand::{thread_rng, Rng}; - /// use rand::distributions::{Alphanumeric, Uniform, Standard}; + /// use rand::distr::{Alphanumeric, Uniform, Standard}; /// /// let mut rng = thread_rng(); /// @@ -213,7 +213,7 @@ pub trait Rng: RngCore { /// println!("Not a 6; rolling again!"); /// } /// ``` - fn sample_iter(self, distr: D) -> distributions::DistIter + fn sample_iter(self, distr: D) -> distr::DistIter where D: Distribution, Self: Sized, @@ -259,11 +259,11 @@ pub trait Rng: RngCore { /// /// If `p < 0` or `p > 1`. /// - /// [`Bernoulli`]: distributions::Bernoulli + /// [`Bernoulli`]: distr::Bernoulli #[inline] #[track_caller] fn gen_bool(&mut self, p: f64) -> bool { - match distributions::Bernoulli::new(p) { + match distr::Bernoulli::new(p) { Ok(d) => self.sample(d), Err(_) => panic!("p={:?} is outside range [0.0, 1.0]", p), } @@ -291,11 +291,11 @@ pub trait Rng: RngCore { /// println!("{}", rng.gen_ratio(2, 3)); /// ``` /// - /// [`Bernoulli`]: distributions::Bernoulli + /// [`Bernoulli`]: distr::Bernoulli #[inline] #[track_caller] fn gen_ratio(&mut self, numerator: u32, denominator: u32) -> bool { - match distributions::Bernoulli::from_ratio(numerator, denominator) { + match distr::Bernoulli::from_ratio(numerator, denominator) { Ok(d) => self.sample(d), Err(_) => panic!( "p={}/{} is outside range [0.0, 1.0]", @@ -554,7 +554,7 @@ mod test { #[test] fn test_rng_trait_object() { - use crate::distributions::{Distribution, Standard}; + use crate::distr::{Distribution, Standard}; let mut rng = rng(109); let mut r = &mut rng as &mut dyn RngCore; r.next_u32(); @@ -566,7 +566,7 @@ mod test { #[test] #[cfg(feature = "alloc")] fn test_rng_boxed_trait() { - use crate::distributions::{Distribution, Standard}; + use crate::distr::{Distribution, Standard}; let rng = rng(110); let mut r = Box::new(rng) as Box; r.next_u32(); diff --git a/src/rngs/mock.rs b/src/rngs/mock.rs index a01a6bd7b4e..6218626db9b 100644 --- a/src/rngs/mock.rs +++ b/src/rngs/mock.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; /// Other integer types (64-bit and smaller) are produced via cast from `u64`. /// /// Other types are produced via their implementation of [`Rng`](crate::Rng) or -/// [`Distribution`](crate::distributions::Distribution). +/// [`Distribution`](crate::distr::Distribution). /// Output values may not be intuitive and may change in future releases but /// are considered /// [portable](https://rust-random.github.io/book/portability.html). @@ -95,7 +95,7 @@ mod tests { #[test] #[cfg(feature = "alloc")] fn test_bool() { - use crate::{distributions::Standard, Rng}; + use crate::{distr::Standard, Rng}; // If this result ever changes, update doc on StepRng! let rng = StepRng::new(0, 1 << 31); diff --git a/src/seq/index.rs b/src/seq/index.rs index e37198bd67a..d7d1636570c 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -7,19 +7,16 @@ // except according to those terms. //! Low-level API for sampling indices -use super::gen_index; -#[cfg(feature = "alloc")] use alloc::vec::{self, Vec}; use core::slice; use core::{hash::Hash, ops::AddAssign}; // BTreeMap is not as fast in tests, but better than nothing. #[cfg(feature = "std")] use super::WeightError; -use crate::distributions::uniform::SampleUniform; -#[cfg(feature = "alloc")] -use crate::distributions::{Distribution, Uniform}; +use crate::distr::uniform::SampleUniform; +use crate::distr::{Distribution, Uniform}; use crate::Rng; -#[cfg(all(feature = "alloc", not(feature = "std")))] +#[cfg(not(feature = "std"))] use alloc::collections::BTreeSet; #[cfg(feature = "serde1")] use serde::{Deserialize, Serialize}; @@ -30,7 +27,6 @@ use std::collections::HashSet; /// /// Multiple internal representations are possible. #[derive(Clone, Debug)] -#[cfg(feature = "alloc")] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub enum IndexVec { #[doc(hidden)] @@ -39,7 +35,6 @@ pub enum IndexVec { USize(Vec), } -#[cfg(feature = "alloc")] impl IndexVec { /// Returns the number of indices #[inline] @@ -90,7 +85,6 @@ impl IndexVec { } } -#[cfg(feature = "alloc")] impl IntoIterator for IndexVec { type IntoIter = IndexVecIntoIter; type Item = usize; @@ -105,7 +99,6 @@ impl IntoIterator for IndexVec { } } -#[cfg(feature = "alloc")] impl PartialEq for IndexVec { fn eq(&self, other: &IndexVec) -> bool { use self::IndexVec::*; @@ -122,7 +115,6 @@ impl PartialEq for IndexVec { } } -#[cfg(feature = "alloc")] impl From> for IndexVec { #[inline] fn from(v: Vec) -> Self { @@ -130,7 +122,6 @@ impl From> for IndexVec { } } -#[cfg(feature = "alloc")] impl From> for IndexVec { #[inline] fn from(v: Vec) -> Self { @@ -171,7 +162,6 @@ impl<'a> Iterator for IndexVecIter<'a> { impl<'a> ExactSizeIterator for IndexVecIter<'a> {} /// Return type of `IndexVec::into_iter`. -#[cfg(feature = "alloc")] #[derive(Clone, Debug)] pub enum IndexVecIntoIter { #[doc(hidden)] @@ -180,7 +170,6 @@ pub enum IndexVecIntoIter { USize(vec::IntoIter), } -#[cfg(feature = "alloc")] impl Iterator for IndexVecIntoIter { type Item = usize; @@ -203,7 +192,6 @@ impl Iterator for IndexVecIntoIter { } } -#[cfg(feature = "alloc")] impl ExactSizeIterator for IndexVecIntoIter {} /// Randomly sample exactly `amount` distinct indices from `0..length`, and @@ -228,7 +216,6 @@ impl ExactSizeIterator for IndexVecIntoIter {} /// to adapt the internal `sample_floyd` implementation. /// /// Panics if `amount > length`. -#[cfg(feature = "alloc")] #[track_caller] pub fn sample(rng: &mut R, length: usize, amount: usize) -> IndexVec where @@ -271,33 +258,6 @@ where } } -/// Randomly sample exactly `N` distinct indices from `0..len`, and -/// return them in random order (fully shuffled). -/// -/// This is implemented via Floyd's algorithm. Time complexity is `O(N^2)` -/// and memory complexity is `O(N)`. -/// -/// Returns `None` if (and only if) `N > len`. -pub fn sample_array(rng: &mut R, len: usize) -> Option<[usize; N]> -where - R: Rng + ?Sized, -{ - if N > len { - return None; - } - - // Floyd's algorithm - let mut indices = [0; N]; - for (i, j) in (len - N..len).enumerate() { - let t = gen_index(rng, j + 1); - if let Some(pos) = indices[0..i].iter().position(|&x| x == t) { - indices[pos] = j; - } - indices[i] = t; - } - Some(indices) -} - /// Randomly sample exactly `amount` distinct indices from `0..length`, and /// return them in an arbitrary order (there is no guarantee of shuffling or /// ordering). The weights are to be provided by the input function `weights`, @@ -432,7 +392,6 @@ where /// The output values are fully shuffled. (Overhead is under 50%.) /// /// This implementation uses `O(amount)` memory and `O(amount^2)` time. -#[cfg(feature = "alloc")] fn sample_floyd(rng: &mut R, length: u32, amount: u32) -> IndexVec where R: Rng + ?Sized, @@ -464,7 +423,6 @@ where /// performance in all cases). /// /// Set-up is `O(length)` time and memory and shuffling is `O(amount)` time. -#[cfg(feature = "alloc")] fn sample_inplace(rng: &mut R, length: u32, amount: u32) -> IndexVec where R: Rng + ?Sized, @@ -483,6 +441,7 @@ where trait UInt: Copy + PartialOrd + Ord + PartialEq + Eq + SampleUniform + Hash + AddAssign { fn zero() -> Self; + #[cfg_attr(feature = "alloc", allow(dead_code))] fn one() -> Self; fn as_usize(self) -> usize; } @@ -530,7 +489,6 @@ impl UInt for usize { /// /// This function is generic over X primarily so that results are value-stable /// over 32-bit and 64-bit platforms. -#[cfg(feature = "alloc")] fn sample_rejection(rng: &mut R, length: X, amount: X) -> IndexVec where R: Rng + ?Sized, @@ -555,7 +513,6 @@ where IndexVec::from(indices) } -#[cfg(feature = "alloc")] #[cfg(test)] mod test { use super::*; diff --git a/src/seq/mod.rs b/src/seq/mod.rs index b7cd1729d21..0015517907a 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -19,7 +19,7 @@ //! //! Also see: //! -//! * [`crate::distributions::WeightedIndex`] distribution which provides +//! * [`crate::distr::WeightedIndex`] distribution which provides //! weighted index sampling. //! //! In order to make results reproducible across 32-64 bit architectures, all @@ -31,11 +31,13 @@ mod increasing_uniform; mod iterator; mod slice; -pub mod index; +#[cfg(feature = "alloc")] +#[path = "index.rs"] +mod index_; #[cfg(feature = "alloc")] #[doc(no_inline)] -pub use crate::distributions::WeightError; +pub use crate::distr::WeightError; pub use iterator::IteratorRandom; #[cfg(feature = "alloc")] pub use slice::SliceChooseIter; @@ -54,3 +56,40 @@ fn gen_index(rng: &mut R, ubound: usize) -> usize { rng.gen_range(0..ubound) } } + +/// Low-level API for sampling indices +pub mod index { + use super::gen_index; + use crate::Rng; + + #[cfg(feature = "alloc")] + #[doc(inline)] + pub use super::index_::*; + + /// Randomly sample exactly `N` distinct indices from `0..len`, and + /// return them in random order (fully shuffled). + /// + /// This is implemented via Floyd's algorithm. Time complexity is `O(N^2)` + /// and memory complexity is `O(N)`. + /// + /// Returns `None` if (and only if) `N > len`. + pub fn sample_array(rng: &mut R, len: usize) -> Option<[usize; N]> + where + R: Rng + ?Sized, + { + if N > len { + return None; + } + + // Floyd's algorithm + let mut indices = [0; N]; + for (i, j) in (len - N..len).enumerate() { + let t = gen_index(rng, j + 1); + if let Some(pos) = indices[0..i].iter().position(|&x| x == t) { + indices[pos] = j; + } + indices[i] = t; + } + Some(indices) + } +} diff --git a/src/seq/slice.rs b/src/seq/slice.rs index c82998fd358..7c86f00a736 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -11,9 +11,9 @@ use super::increasing_uniform::IncreasingUniform; use super::{gen_index, index}; #[cfg(feature = "alloc")] -use crate::distributions::uniform::{SampleBorrow, SampleUniform}; +use crate::distr::uniform::{SampleBorrow, SampleUniform}; #[cfg(feature = "alloc")] -use crate::distributions::{Weight, WeightError}; +use crate::distr::{Weight, WeightError}; use crate::Rng; use core::ops::{Index, IndexMut}; @@ -137,7 +137,7 @@ pub trait IndexedRandom: Index { /// /// For slices of length `n`, complexity is `O(n)`. /// For more information about the underlying algorithm, - /// see [`distributions::WeightedIndex`]. + /// see [`distr::WeightedIndex`]. /// /// See also [`choose_weighted_mut`]. /// @@ -154,7 +154,7 @@ pub trait IndexedRandom: Index { /// ``` /// [`choose`]: IndexedRandom::choose /// [`choose_weighted_mut`]: IndexedMutRandom::choose_weighted_mut - /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex + /// [`distr::WeightedIndex`]: crate::distr::WeightedIndex #[cfg(feature = "alloc")] fn choose_weighted( &self, @@ -167,7 +167,7 @@ pub trait IndexedRandom: Index { B: SampleBorrow, X: SampleUniform + Weight + PartialOrd, { - use crate::distributions::{Distribution, WeightedIndex}; + use crate::distr::{Distribution, WeightedIndex}; let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; Ok(&self[distr.sample(rng)]) } @@ -268,13 +268,13 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { /// /// For slices of length `n`, complexity is `O(n)`. /// For more information about the underlying algorithm, - /// see [`distributions::WeightedIndex`]. + /// see [`distr::WeightedIndex`]. /// /// See also [`choose_weighted`]. /// /// [`choose_mut`]: IndexedMutRandom::choose_mut /// [`choose_weighted`]: IndexedRandom::choose_weighted - /// [`distributions::WeightedIndex`]: crate::distributions::WeightedIndex + /// [`distr::WeightedIndex`]: crate::distr::WeightedIndex #[cfg(feature = "alloc")] fn choose_weighted_mut( &mut self, @@ -287,7 +287,7 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { B: SampleBorrow, X: SampleUniform + Weight + PartialOrd, { - use crate::distributions::{Distribution, WeightedIndex}; + use crate::distr::{Distribution, WeightedIndex}; let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; let index = distr.sample(rng); Ok(&mut self[index]) diff --git a/utils/ziggurat_tables.py b/utils/ziggurat_tables.py index 88cfdab6ba2..87a766ccc36 100755 --- a/utils/ziggurat_tables.py +++ b/utils/ziggurat_tables.py @@ -10,7 +10,7 @@ # except according to those terms. # This creates the tables used for distributions implemented using the -# ziggurat algorithm in `rand::distributions;`. They are +# ziggurat algorithm in `rand::distr;`. They are # (basically) the tables as used in the ZIGNOR variant (Doornik 2005). # They are changed rarely, so the generated file should be checked in # to git. From f7bbccaedf6c63b02855b90b003c9b1a4d1fd1cb Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Fri, 26 Jul 2024 14:48:53 +0700 Subject: [PATCH 400/443] Rename `serde1` feature to `serde`. (#1477) --- .github/workflows/gh-pages.yml | 2 +- .github/workflows/test.yml | 18 +++++++++--------- CHANGELOG.md | 1 + Cargo.toml | 6 +++--- rand_chacha/CHANGELOG.md | 3 +++ rand_chacha/Cargo.toml | 4 ++-- rand_chacha/src/chacha.rs | 16 ++++++++-------- rand_core/CHANGELOG.md | 1 + rand_core/Cargo.toml | 2 +- rand_core/README.md | 2 +- rand_core/src/block.rs | 8 ++++---- rand_distr/CHANGELOG.md | 2 ++ rand_distr/Cargo.toml | 2 +- rand_distr/README.md | 2 +- rand_distr/src/beta.rs | 12 ++++++------ rand_distr/src/binomial.rs | 2 +- rand_distr/src/cauchy.rs | 2 +- rand_distr/src/chi_squared.rs | 8 ++++---- rand_distr/src/dirichlet.rs | 2 +- rand_distr/src/exponential.rs | 4 ++-- rand_distr/src/fisher_f.rs | 6 +++--- rand_distr/src/frechet.rs | 2 +- rand_distr/src/gamma.rs | 10 +++++----- rand_distr/src/geometric.rs | 4 ++-- rand_distr/src/gumbel.rs | 2 +- rand_distr/src/hypergeometric.rs | 4 ++-- rand_distr/src/inverse_gaussian.rs | 2 +- rand_distr/src/normal.rs | 6 +++--- rand_distr/src/normal_inverse_gaussian.rs | 2 +- rand_distr/src/pareto.rs | 2 +- rand_distr/src/pert.rs | 2 +- rand_distr/src/poisson.rs | 2 +- rand_distr/src/skew_normal.rs | 2 +- rand_distr/src/student_t.rs | 4 ++-- rand_distr/src/triangular.rs | 2 +- rand_distr/src/unit_ball.rs | 2 +- rand_distr/src/unit_circle.rs | 2 +- rand_distr/src/unit_disc.rs | 2 +- rand_distr/src/unit_sphere.rs | 2 +- rand_distr/src/weibull.rs | 2 +- rand_distr/src/weighted_alias.rs | 8 ++++---- rand_distr/src/weighted_tree.rs | 8 ++++---- rand_pcg/CHANGELOG.md | 3 +++ rand_pcg/Cargo.toml | 2 +- rand_pcg/README.md | 2 +- rand_pcg/src/pcg128.rs | 6 +++--- rand_pcg/src/pcg128cm.rs | 4 ++-- rand_pcg/src/pcg64.rs | 4 ++-- rand_pcg/tests/lcg128cmdxsm64.rs | 2 +- rand_pcg/tests/lcg128xsl64.rs | 2 +- rand_pcg/tests/lcg64xsh32.rs | 2 +- rand_pcg/tests/mcg128xsl64.rs | 2 +- src/distr/bernoulli.rs | 6 +++--- src/distr/float.rs | 6 +++--- src/distr/mod.rs | 2 +- src/distr/other.rs | 4 ++-- src/distr/uniform.rs | 10 +++++----- src/distr/uniform_float.rs | 4 ++-- src/distr/uniform_int.rs | 4 ++-- src/distr/uniform_other.rs | 10 +++++----- src/distr/weighted_index.rs | 8 ++++---- src/rngs/mock.rs | 8 ++++---- src/rngs/xoshiro128plusplus.rs | 4 ++-- src/rngs/xoshiro256plusplus.rs | 4 ++-- src/seq/index.rs | 6 +++--- 65 files changed, 146 insertions(+), 136 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 3b8f20b7143..c0cab03b926 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -30,7 +30,7 @@ jobs: RUSTDOCFLAGS: --cfg doc_cfg # --all builds all crates, but with default features for other crates (okay in this case) run: | - cargo doc --all --features nightly,serde1,getrandom,small_rng + cargo doc --all --features nightly,serde,getrandom,small_rng cp utils/redirect.html target/doc/index.html rm target/doc/.lock diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c8d1892554f..fadd73f4aa2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -95,7 +95,7 @@ jobs: cargo test --target ${{ matrix.target }} --examples - name: Test rand (all stable features) run: | - cargo test --target ${{ matrix.target }} --features=serde1,log,small_rng + cargo test --target ${{ matrix.target }} --features=serde,log,small_rng - name: Test rand_core run: | cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml @@ -103,13 +103,13 @@ jobs: cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features --features=alloc,getrandom - name: Test rand_distr run: | - cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde1 + cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --no-default-features cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --no-default-features --features=std,std_math - name: Test rand_pcg - run: cargo test --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde1 + run: cargo test --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde - name: Test rand_chacha - run: cargo test --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml --features=serde1 + run: cargo test --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml --features=serde test-cross: runs-on: ${{ matrix.os }} @@ -138,11 +138,11 @@ jobs: - name: Test run: | # all stable features: - cross test --no-fail-fast --target ${{ matrix.target }} --features=serde1,log,small_rng + cross test --no-fail-fast --target ${{ matrix.target }} --features=serde,log,small_rng cross test --no-fail-fast --target ${{ matrix.target }} --examples cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml - cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde1 - cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde1 + cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde + cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_pcg/Cargo.toml --features=serde cross test --no-fail-fast --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml test-miri: @@ -159,10 +159,10 @@ jobs: cargo miri test --no-default-features --lib --tests cargo miri test --features=log,small_rng cargo miri test --manifest-path rand_core/Cargo.toml - cargo miri test --manifest-path rand_core/Cargo.toml --features=serde1 + cargo miri test --manifest-path rand_core/Cargo.toml --features=serde cargo miri test --manifest-path rand_core/Cargo.toml --no-default-features #cargo miri test --manifest-path rand_distr/Cargo.toml # no unsafe and lots of slow tests - cargo miri test --manifest-path rand_pcg/Cargo.toml --features=serde1 + cargo miri test --manifest-path rand_pcg/Cargo.toml --features=serde cargo miri test --manifest-path rand_chacha/Cargo.toml --no-default-features test-no-std: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a098453c0e..205cf4c14b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) - Fix portability of `rand::distributions::Slice` (#1469) - Rename `rand::distributions` to `rand::distr` (#1470) +- The `serde1` feature has been renamed `serde` (#1477) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/Cargo.toml b/Cargo.toml index 740f799a56a..9924c007234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,13 +24,13 @@ all-features = true rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] [package.metadata.playground] -features = ["small_rng", "serde1"] +features = ["small_rng", "serde"] [features] # Meta-features: default = ["std", "std_rng", "getrandom", "small_rng"] nightly = [] # some additions requiring nightly Rust -serde1 = ["serde", "rand_core/serde1"] +serde = ["dep:serde", "rand_core/serde"] # Option (enabled by default): without "std" rand uses libcore; this option # enables functionality expected to be available on a standard platform. @@ -74,6 +74,6 @@ zerocopy = { version = "0.7.33", default-features = false, features = ["simd"] } [dev-dependencies] rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } -# Only to test serde1 +# Only to test serde bincode = "1.2.1" rayon = "1.7" diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index d74dc1f9b0e..8543c1e4033 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +- The `serde1` feature has been renamed `serde` (#1477) + ## [0.9.0-alpha.1] - 2024-03-18 ## [0.9.0-alpha.0] - 2024-02-18 diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 981cb7a6a15..c15389cdc08 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -25,7 +25,7 @@ ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] -# Only to test serde1 +# Only to test serde serde_json = "1.0" rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["getrandom"] } @@ -33,4 +33,4 @@ rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["ge default = ["std"] getrandom = ["rand_core/getrandom"] std = ["ppv-lite86/std", "rand_core/std"] -serde1 = ["serde"] +serde = ["dep:serde"] diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 3da47e45d76..297095f6486 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -18,7 +18,7 @@ use crate::guts::ChaCha; use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; // NB. this must remain consistent with some currently hard-coded numbers in this module @@ -276,7 +276,7 @@ macro_rules! chacha_impl { } impl Eq for $ChaChaXRng {} - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] impl Serialize for $ChaChaXRng { fn serialize(&self, s: S) -> Result where @@ -285,7 +285,7 @@ macro_rules! chacha_impl { $abst::$ChaChaXRng::from(self).serialize(s) } } - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for $ChaChaXRng { fn deserialize(d: D) -> Result where @@ -296,14 +296,14 @@ macro_rules! chacha_impl { } mod $abst { - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; // The abstract state of a ChaCha stream, independent of implementation choices. The // comparison and serialization of this object is considered a semver-covered part of // the API. #[derive(Debug, PartialEq, Eq)] - #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub(crate) struct $ChaChaXRng { seed: [u8; 32], stream: u64, @@ -362,12 +362,12 @@ chacha_impl!( mod test { use rand_core::{RngCore, SeedableRng}; - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] use super::{ChaCha12Rng, ChaCha20Rng, ChaCha8Rng}; type ChaChaRng = super::ChaCha20Rng; - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] #[test] fn test_chacha_serde_roundtrip() { let seed = [ @@ -405,7 +405,7 @@ mod test { // However testing for equivalence of serialized data is difficult, and there shouldn't be any // reason we need to violate the stronger-than-needed condition, e.g. by changing the field // definition order. - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] #[test] fn test_chacha_serde_format_stability() { let j = r#"{"seed":[4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8,15,16,23,42,4,8],"stream":27182818284,"word_pos":314159265359}"#; diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index ec29912baa0..d422fe30bd4 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Bump the MSRV to 1.61.0 +- The `serde1` feature has been renamed `serde` (#1477) ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 0053b40245f..dc4084d69ae 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -27,7 +27,7 @@ all-features = true [features] std = ["alloc", "getrandom?/std"] alloc = [] # enables Vec and Box support without std -serde1 = ["serde"] # enables serde for BlockRng wrapper +serde = ["dep:serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } diff --git a/rand_core/README.md b/rand_core/README.md index 31c742d9f3e..98fb7c9a79c 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -67,7 +67,7 @@ problems where one crate implicitly requires `rand_core` with `std` support and another crate requires `rand` *without* `std` support. However, the `rand` crate continues to enable `std` support by default, both for itself and `rand_core`. -The `serde1` feature can be used to derive `Serialize` and `Deserialize` for RNG +The `serde` feature can be used to derive `Serialize` and `Deserialize` for RNG implementations that use the `BlockRng` or `BlockRng64` wrappers. diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 9f227bb75b4..36072ba355b 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -56,7 +56,7 @@ use crate::impls::{fill_via_u32_chunks, fill_via_u64_chunks}; use crate::{CryptoRng, RngCore, SeedableRng, TryRngCore}; use core::fmt; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A trait for RNGs which do not generate random numbers individually, but in @@ -116,9 +116,9 @@ pub trait CryptoBlockRng: BlockRngCore {} /// [`next_u64`]: RngCore::next_u64 /// [`fill_bytes`]: RngCore::fill_bytes #[derive(Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( - feature = "serde1", + feature = "serde", serde( bound = "for<'x> R: Serialize + Deserialize<'x> + Sized, for<'x> R::Results: Serialize + Deserialize<'x>" ) @@ -283,7 +283,7 @@ impl> CryptoRng for BlockRng {} /// [`next_u64`]: RngCore::next_u64 /// [`fill_bytes`]: RngCore::fill_bytes #[derive(Clone)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BlockRng64 { results: R::Results, index: usize, diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 570474bee44..51bde39e86b 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +- The `serde1` feature has been renamed `serde` (#1477) + ### Added - Add plots for `rand_distr` distributions to documentation (#1434) - Add `PertBuilder`, fix case where mode ≅ mean (#1452) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 4f21c62b132..c3a717a8465 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -29,7 +29,7 @@ alloc = ["rand/alloc"] # feature (default-enabled) will have the same effect. std_math = ["num-traits/std"] -serde1 = ["serde", "rand/serde1"] +serde = ["dep:serde", "rand/serde"] [dependencies] rand = { path = "..", version = "=0.9.0-alpha.1", default-features = false } diff --git a/rand_distr/README.md b/rand_distr/README.md index 0c8b20b95ef..78d26b29a73 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -35,7 +35,7 @@ can be enabled. (Note that any other crate depending on `num-traits` with the - `alloc` (enabled by default): required for some distributions when not using `std` (in particular, `Dirichlet` and `WeightedAliasIndex`). - `std_math`: see above on portability and libm -- `serde1`: implement (de)seriaialization using `serde` +- `serde`: implement (de)seriaialization using `serde` ## Links diff --git a/rand_distr/src/beta.rs b/rand_distr/src/beta.rs index 9ef5d0024df..da3106a5050 100644 --- a/rand_distr/src/beta.rs +++ b/rand_distr/src/beta.rs @@ -13,7 +13,7 @@ use crate::{Distribution, Open01}; use core::fmt; use num_traits::Float; use rand::Rng; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// The algorithm used for sampling the Beta distribution. @@ -25,7 +25,7 @@ use serde::{Deserialize, Serialize}; /// Communications of the ACM 21, 317-322. /// https://doi.org/10.1145/359460.359482 #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] enum BetaAlgorithm { BB(BB), BC(BC), @@ -33,7 +33,7 @@ enum BetaAlgorithm { /// Algorithm BB for `min(alpha, beta) > 1`. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] struct BB { alpha: N, beta: N, @@ -42,7 +42,7 @@ struct BB { /// Algorithm BC for `min(alpha, beta) <= 1`. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] struct BC { alpha: N, beta: N, @@ -77,7 +77,7 @@ struct BC { /// println!("{} is from a Beta(2, 5) distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Beta where F: Float, @@ -91,7 +91,7 @@ where /// Error type returned from [`Beta::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Error { /// `alpha <= 0` or `nan`. AlphaTooSmall, diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 02f7fc37ff0..fa061b0333d 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -44,7 +44,7 @@ use rand::Rng; /// println!("{} is from a binomial distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Binomial { /// Number of trials. n: u64, diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 1c6f91236cc..e19c415df8a 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -54,7 +54,7 @@ use rand::Rng; /// Note that at least for `f32`, results are not fully portable due to minor /// differences in the target system's *tan* implementation, `tanf`. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Cauchy where F: Float + FloatConst, diff --git a/rand_distr/src/chi_squared.rs b/rand_distr/src/chi_squared.rs index fcdc397bf6e..c5cf41236c5 100644 --- a/rand_distr/src/chi_squared.rs +++ b/rand_distr/src/chi_squared.rs @@ -15,7 +15,7 @@ use crate::{Distribution, Exp1, Gamma, Open01, StandardNormal}; use core::fmt; use num_traits::Float; use rand::Rng; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// The [chi-squared distribution](https://en.wikipedia.org/wiki/Chi-squared_distribution) `χ²(k)`. @@ -45,7 +45,7 @@ use serde::{Deserialize, Serialize}; /// println!("{} is from a χ²(11) distribution", v) /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ChiSquared where F: Float, @@ -58,7 +58,7 @@ where /// Error type returned from [`ChiSquared::new`] and [`StudentT::new`](crate::StudentT::new). #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Error { /// `0.5 * k <= 0` or `nan`. DoFTooSmall, @@ -78,7 +78,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] enum ChiSquaredRepr where F: Float, diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 3cd4c09eea7..c7a6eda92d9 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -92,7 +92,7 @@ where } #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] struct DirichletFromBeta where F: Float, diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index c31fc9520ec..1d23757190b 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -49,7 +49,7 @@ use rand::Rng; /// https://www.doornik.com/research/ziggurat.pdf). /// Nuffield College, Oxford #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Exp1; impl Distribution for Exp1 { @@ -120,7 +120,7 @@ impl Distribution for Exp1 { /// println!("{} is from a Exp(2) distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Exp where F: Float, diff --git a/rand_distr/src/fisher_f.rs b/rand_distr/src/fisher_f.rs index 1a16b6d6363..532b3237d7e 100644 --- a/rand_distr/src/fisher_f.rs +++ b/rand_distr/src/fisher_f.rs @@ -13,7 +13,7 @@ use crate::{ChiSquared, Distribution, Exp1, Open01, StandardNormal}; use core::fmt; use num_traits::Float; use rand::Rng; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// The [Fisher F-distribution](https://en.wikipedia.org/wiki/F-distribution) `F(m, n)`. @@ -38,7 +38,7 @@ use serde::{Deserialize, Serialize}; /// println!("{} is from an F(2, 32) distribution", v) /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct FisherF where F: Float, @@ -55,7 +55,7 @@ where /// Error type returned from [`FisherF::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Error { /// `m <= 0` or `nan`. MTooSmall, diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index 831561d607e..72dd6032dbe 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -44,7 +44,7 @@ use rand::Rng; /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Frechet where F: Float, diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 4699bbb6e81..8edda3a1abc 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -15,7 +15,7 @@ use crate::{Distribution, Exp, Exp1, Open01, StandardNormal}; use core::fmt; use num_traits::Float; use rand::Rng; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// The [Gamma distribution](https://en.wikipedia.org/wiki/Gamma_distribution) `Gamma(k, θ)`. @@ -63,7 +63,7 @@ use serde::{Deserialize, Serialize}; /// (September 2000), 363-372. /// DOI:[10.1145/358407.358414](https://doi.acm.org/10.1145/358407.358414) #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Gamma where F: Float, @@ -99,7 +99,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] enum GammaRepr where F: Float, @@ -127,7 +127,7 @@ where /// See `Gamma` for sampling from a Gamma distribution with general /// shape parameters. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] struct GammaSmallShape where F: Float, @@ -143,7 +143,7 @@ where /// See `Gamma` for sampling from a Gamma distribution with general /// shape parameters. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] struct GammaLargeShape where F: Float, diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index 9beb9382281..b6c899e0e78 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -39,7 +39,7 @@ use rand::Rng; /// println!("{} is from a Geometric(0.25) distribution", v); /// ``` #[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Geometric { p: f64, pi: f64, @@ -176,7 +176,7 @@ impl Distribution for Geometric { /// Implemented via iterated /// [`Rng::gen::().leading_zeros()`](Rng::gen::().leading_zeros()). #[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct StandardGeometric; impl Distribution for StandardGeometric { diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 6a7f1ae7b94..7ed326268b4 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -42,7 +42,7 @@ use rand::Rng; /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Gumbel where F: Float, diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index 683db15314d..d17af2fdb76 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -8,7 +8,7 @@ use rand::distr::uniform::Uniform; use rand::Rng; #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] enum SamplingMethod { InverseTransform { initial_p: f64, @@ -58,7 +58,7 @@ enum SamplingMethod { /// println!("{} is from a hypergeometric distribution", v); /// ``` #[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Hypergeometric { n1: u64, n2: u64, diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 4a5aad7982c..06a9b72e858 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -48,7 +48,7 @@ impl std::error::Error for Error {} /// println!("{} is from a inverse Gaussian(1, 2) distribution", v); /// ``` #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct InverseGaussian where F: Float, diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index f6e6adc4206..ab95cad74e6 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -45,7 +45,7 @@ use rand::Rng; /// https://www.doornik.com/research/ziggurat.pdf). /// Nuffield College, Oxford #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct StandardNormal; impl Distribution for StandardNormal { @@ -143,7 +143,7 @@ impl Distribution for StandardNormal { /// https://www.doornik.com/research/ziggurat.pdf). /// Nuffield College, Oxford #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Normal where F: Float, @@ -270,7 +270,7 @@ where /// println!("{} is from an ln N(2, 9) distribution", v) /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct LogNormal where F: Float, diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index 2c7fe71a31e..1c60b87d1bd 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -49,7 +49,7 @@ impl std::error::Error for Error {} /// println!("{} is from a normal-inverse Gaussian(2, 1) distribution", v); /// ``` #[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct NormalInverseGaussian where F: Float, diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index f8b86c7035c..250891d37f1 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -36,7 +36,7 @@ use rand::Rng; /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Pareto where F: Float, diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index ae268dad7b5..b14d0e1ff59 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -38,7 +38,7 @@ use rand::Rng; /// /// [`Triangular`]: crate::Triangular #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Pert where F: Float, diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 78675ad9c41..fd631a256fe 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -40,7 +40,7 @@ use rand::Rng; /// println!("{} is from a Poisson(2) distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Poisson where F: Float + FloatConst, diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 8ef88428a4e..6cba670b17c 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -57,7 +57,7 @@ use rand::Rng; /// [`Normal`]: struct.Normal.html /// [A Method to Simulate the Skew Normal Distribution]: https://dx.doi.org/10.4236/am.2014.513201 #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct SkewNormal where F: Float, diff --git a/rand_distr/src/student_t.rs b/rand_distr/src/student_t.rs index 86a5fb5b456..a84a0961604 100644 --- a/rand_distr/src/student_t.rs +++ b/rand_distr/src/student_t.rs @@ -13,7 +13,7 @@ use crate::{ChiSquared, ChiSquaredError}; use crate::{Distribution, Exp1, Open01, StandardNormal}; use num_traits::Float; use rand::Rng; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// The [Student t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) `t(ν)`. @@ -46,7 +46,7 @@ use serde::{Deserialize, Serialize}; /// println!("{} is from a t(11) distribution", v) /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct StudentT where F: Float, diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index ab7722ac030..c1b151a576f 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -39,7 +39,7 @@ use rand::Rng; /// /// [`Pert`]: crate::Pert #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Triangular where F: Float, diff --git a/rand_distr/src/unit_ball.rs b/rand_distr/src/unit_ball.rs index 1cc7119b7fb..025c1557ad7 100644 --- a/rand_distr/src/unit_ball.rs +++ b/rand_distr/src/unit_ball.rs @@ -36,7 +36,7 @@ use rand::Rng; /// println!("{:?} is from the unit ball.", v) /// ``` #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct UnitBall; impl Distribution<[F; 3]> for UnitBall { diff --git a/rand_distr/src/unit_circle.rs b/rand_distr/src/unit_circle.rs index a23cec25225..3caee8f629f 100644 --- a/rand_distr/src/unit_circle.rs +++ b/rand_distr/src/unit_circle.rs @@ -39,7 +39,7 @@ use rand::Rng; /// NBS Appl. Math. Ser., No. 12. Washington, DC: U.S. Government Printing /// Office, pp. 36-38. #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct UnitCircle; impl Distribution<[F; 2]> for UnitCircle { diff --git a/rand_distr/src/unit_disc.rs b/rand_distr/src/unit_disc.rs index 4ba5256265f..a472de2a339 100644 --- a/rand_distr/src/unit_disc.rs +++ b/rand_distr/src/unit_disc.rs @@ -35,7 +35,7 @@ use rand::Rng; /// println!("{:?} is from the unit Disc.", v) /// ``` #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct UnitDisc; impl Distribution<[F; 2]> for UnitDisc { diff --git a/rand_distr/src/unit_sphere.rs b/rand_distr/src/unit_sphere.rs index 61f48e0c067..2edd0f83798 100644 --- a/rand_distr/src/unit_sphere.rs +++ b/rand_distr/src/unit_sphere.rs @@ -40,7 +40,7 @@ use rand::Rng; /// Sphere.*](https://doi.org/10.1214/aoms/1177692644) /// Ann. Math. Statist. 43, no. 2, 645--646. #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct UnitSphere; impl Distribution<[F; 3]> for UnitSphere { diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index 145a4df3883..3d11c7270cf 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -34,7 +34,7 @@ use rand::Rng; /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Weibull where F: Float, diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index e8c455dcb1b..8826b5b76c5 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -16,7 +16,7 @@ use core::fmt; use core::iter::Sum; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use rand::Rng; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A distribution using weighted sampling to pick a discretely selected item. @@ -65,13 +65,13 @@ use serde::{Deserialize, Serialize}; /// [`Vec`]: Vec /// [`Uniform::sample`]: Distribution::sample /// [`Uniform::sample`]: Distribution::sample -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( - feature = "serde1", + feature = "serde", serde(bound(serialize = "W: Serialize, W::Sampler: Serialize")) )] #[cfg_attr( - feature = "serde1", + feature = "serde", serde(bound(deserialize = "W: Deserialize<'de>, W::Sampler: Deserialize<'de>")) )] pub struct WeightedAliasIndex { diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index 28edab700d4..ef95ba41923 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -17,7 +17,7 @@ use alloc::vec::Vec; use rand::distr::uniform::{SampleBorrow, SampleUniform}; use rand::distr::Weight; use rand::Rng; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A distribution using weighted sampling to pick a discretely selected item. @@ -77,13 +77,13 @@ use serde::{Deserialize, Serialize}; /// ``` /// /// [`WeightedTreeIndex`]: WeightedTreeIndex -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( - feature = "serde1", + feature = "serde", serde(bound(serialize = "W: Serialize, W::Sampler: Serialize")) )] #[cfg_attr( - feature = "serde1", + feature = "serde", serde(bound(deserialize = "W: Deserialize<'de>, W::Sampler: Deserialize<'de>")) )] #[derive(Clone, Default, Debug, PartialEq)] diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index 18ea4229732..80940a93a07 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] +- The `serde1` feature has been renamed `serde` (#1477) + ## [0.9.0-alpha.1] - 2024-03-18 ## [0.9.0-alpha.0] - 2024-02-18 diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 30c3026fef0..1bd7398ca6a 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -20,7 +20,7 @@ all-features = true rustdoc-args = ["--generate-link-to-definition"] [features] -serde1 = ["serde"] +serde = ["dep:serde"] getrandom = ["rand_core/getrandom"] [dependencies] diff --git a/rand_pcg/README.md b/rand_pcg/README.md index cb6f096492a..79535b8cda3 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -29,7 +29,7 @@ Links: `rand_pcg` is `no_std` compatible by default. -The `serde1` feature includes implementations of `Serialize` and `Deserialize` +The `serde` feature includes implementations of `Serialize` and `Deserialize` for the included RNGs. ## License diff --git a/rand_pcg/src/pcg128.rs b/rand_pcg/src/pcg128.rs index 61f54c4a85d..990303c41fb 100644 --- a/rand_pcg/src/pcg128.rs +++ b/rand_pcg/src/pcg128.rs @@ -15,7 +15,7 @@ const MULTIPLIER: u128 = 0x2360_ED05_1FC6_5DA4_4385_DF64_9FCC_F645; use core::fmt; use rand_core::{impls, le, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A PCG random number generator (XSL RR 128/64 (LCG) variant). @@ -34,7 +34,7 @@ use serde::{Deserialize, Serialize}; /// Note that two generators with different stream parameters may be closely /// correlated. #[derive(Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Lcg128Xsl64 { state: u128, increment: u128, @@ -166,7 +166,7 @@ impl RngCore for Lcg128Xsl64 { /// output function), this RNG is faster, also has a long cycle, and still has /// good performance on statistical tests. #[derive(Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Mcg128Xsl64 { state: u128, } diff --git a/rand_pcg/src/pcg128cm.rs b/rand_pcg/src/pcg128cm.rs index 6910f3458e1..a5a2b178795 100644 --- a/rand_pcg/src/pcg128cm.rs +++ b/rand_pcg/src/pcg128cm.rs @@ -15,7 +15,7 @@ const MULTIPLIER: u64 = 15750249268501108917; use core::fmt; use rand_core::{impls, le, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A PCG random number generator (CM DXSM 128/64 (LCG) variant). @@ -37,7 +37,7 @@ use serde::{Deserialize, Serialize}; /// /// [upgrading-pcg64]: https://numpy.org/doc/stable/reference/random/upgrading-pcg64.html #[derive(Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Lcg128CmDxsm64 { state: u128, increment: u128, diff --git a/rand_pcg/src/pcg64.rs b/rand_pcg/src/pcg64.rs index 8b2df6aa618..771a996d28f 100644 --- a/rand_pcg/src/pcg64.rs +++ b/rand_pcg/src/pcg64.rs @@ -12,7 +12,7 @@ use core::fmt; use rand_core::{impls, le, RngCore, SeedableRng}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; // This is the default multiplier used by PCG for 64-bit state. @@ -34,7 +34,7 @@ const MULTIPLIER: u64 = 6364136223846793005; /// Note that two generators with different stream parameter may be closely /// correlated. #[derive(Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Lcg64Xsh32 { state: u64, increment: u64, diff --git a/rand_pcg/tests/lcg128cmdxsm64.rs b/rand_pcg/tests/lcg128cmdxsm64.rs index d5f3a6a498d..b5b37f582e0 100644 --- a/rand_pcg/tests/lcg128cmdxsm64.rs +++ b/rand_pcg/tests/lcg128cmdxsm64.rs @@ -54,7 +54,7 @@ fn test_lcg128cmdxsm64_reference() { assert_eq!(results, expected); } -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] #[test] fn test_lcg128cmdxsm64_serde() { use bincode; diff --git a/rand_pcg/tests/lcg128xsl64.rs b/rand_pcg/tests/lcg128xsl64.rs index 92c9a41f57d..07bd6137da9 100644 --- a/rand_pcg/tests/lcg128xsl64.rs +++ b/rand_pcg/tests/lcg128xsl64.rs @@ -54,7 +54,7 @@ fn test_lcg128xsl64_reference() { assert_eq!(results, expected); } -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] #[test] fn test_lcg128xsl64_serde() { use bincode; diff --git a/rand_pcg/tests/lcg64xsh32.rs b/rand_pcg/tests/lcg64xsh32.rs index ee884706fb4..ea704a50f6f 100644 --- a/rand_pcg/tests/lcg64xsh32.rs +++ b/rand_pcg/tests/lcg64xsh32.rs @@ -47,7 +47,7 @@ fn test_lcg64xsh32_reference() { assert_eq!(results, expected); } -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] #[test] fn test_lcg64xsh32_serde() { use bincode; diff --git a/rand_pcg/tests/mcg128xsl64.rs b/rand_pcg/tests/mcg128xsl64.rs index bee87aeb3f7..6125f1998c2 100644 --- a/rand_pcg/tests/mcg128xsl64.rs +++ b/rand_pcg/tests/mcg128xsl64.rs @@ -52,7 +52,7 @@ fn test_mcg128xsl64_reference() { assert_eq!(results, expected); } -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] #[test] fn test_mcg128xsl64_serde() { use bincode; diff --git a/src/distr/bernoulli.rs b/src/distr/bernoulli.rs index 5a56d079a83..d71660b985f 100644 --- a/src/distr/bernoulli.rs +++ b/src/distr/bernoulli.rs @@ -12,7 +12,7 @@ use crate::distr::Distribution; use crate::Rng; use core::fmt; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// The [Bernoulli distribution](https://en.wikipedia.org/wiki/Bernoulli_distribution) `Bernoulli(p)`. @@ -44,7 +44,7 @@ use serde::{Deserialize, Serialize}; /// so only probabilities that are multiples of 2-64 can be /// represented. #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Bernoulli { /// Probability of success, relative to the maximal integer. p_int: u64, @@ -157,7 +157,7 @@ mod test { use crate::Rng; #[test] - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] fn test_serializing_deserializing_bernoulli() { let coin_flip = Bernoulli::new(0.5).unwrap(); let de_coin_flip: Bernoulli = diff --git a/src/distr/float.rs b/src/distr/float.rs index 67e6d4b250d..0732b0afe55 100644 --- a/src/distr/float.rs +++ b/src/distr/float.rs @@ -15,7 +15,7 @@ use core::mem; #[cfg(feature = "simd_support")] use core::simd::prelude::*; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A distribution to sample floating point numbers uniformly in the half-open @@ -43,7 +43,7 @@ use serde::{Deserialize, Serialize}; /// [`Open01`]: crate::distr::Open01 /// [`Uniform`]: crate::distr::uniform::Uniform #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct OpenClosed01; /// A distribution to sample floating point numbers uniformly in the open @@ -70,7 +70,7 @@ pub struct OpenClosed01; /// [`OpenClosed01`]: crate::distr::OpenClosed01 /// [`Uniform`]: crate::distr::uniform::Uniform #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Open01; // This trait is needed by both this lib and rand_distr hence is a hidden export diff --git a/src/distr/mod.rs b/src/distr/mod.rs index 8b5c0054271..668c9ffda7f 100644 --- a/src/distr/mod.rs +++ b/src/distr/mod.rs @@ -218,5 +218,5 @@ use crate::Rng; /// [`mask32x4`]: std::simd::mask32x4 /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Standard; diff --git a/src/distr/other.rs b/src/distr/other.rs index 3ce24e00a01..ef10828d1c6 100644 --- a/src/distr/other.rs +++ b/src/distr/other.rs @@ -23,7 +23,7 @@ use core::mem::{self, MaybeUninit}; use core::simd::prelude::*; #[cfg(feature = "simd_support")] use core::simd::{LaneCount, MaskElement, SupportedLaneCount}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; // ----- Sampling distributions ----- @@ -67,7 +67,7 @@ use serde::{Deserialize, Serialize}; /// - [Wikipedia article on Password Strength](https://en.wikipedia.org/wiki/Password_strength) /// - [Diceware for generating memorable passwords](https://en.wikipedia.org/wiki/Diceware) #[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Alphanumeric; // ----- Implementations of distributions ----- diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index fa245c3aaf8..4f07aaf26eb 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -146,7 +146,7 @@ impl fmt::Display for Error { #[cfg(feature = "std")] impl std::error::Error for Error {} -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Sample values uniformly between two bounds. @@ -199,10 +199,10 @@ use serde::{Deserialize, Serialize}; /// [`new_inclusive`]: Uniform::new_inclusive /// [`Rng::gen_range`]: Rng::gen_range #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(bound(serialize = "X::Sampler: Serialize")))] #[cfg_attr( - feature = "serde1", + feature = "serde", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")) )] pub struct Uniform(X::Sampler); @@ -445,7 +445,7 @@ mod tests { use core::time::Duration; #[test] - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] fn test_uniform_serialization() { let unit_box: Uniform = Uniform::new(-1, 1).unwrap(); let de_unit_box: Uniform = diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index b44e192c65d..2fd29b2383d 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -19,7 +19,7 @@ use core::simd::prelude::*; // #[cfg(feature = "simd_support")] // use core::simd::{LaneCount, SupportedLaneCount}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// The back-end implementing [`UniformSampler`] for floating-point types. @@ -43,7 +43,7 @@ use serde::{Deserialize, Serialize}; /// [`Standard`]: crate::distr::Standard /// [`Uniform`]: super::Uniform #[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct UniformFloat { low: X, scale: X, diff --git a/src/distr/uniform_int.rs b/src/distr/uniform_int.rs index 5a6254058e0..4a5bb319910 100644 --- a/src/distr/uniform_int.rs +++ b/src/distr/uniform_int.rs @@ -20,7 +20,7 @@ use core::simd::prelude::*; #[cfg(feature = "simd_support")] use core::simd::{LaneCount, SupportedLaneCount}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// The back-end implementing [`UniformSampler`] for integer types. @@ -60,7 +60,7 @@ use serde::{Deserialize, Serialize}; /// /// [`Uniform`]: super::Uniform #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct UniformInt { pub(super) low: X, pub(super) range: X, diff --git a/src/distr/uniform_other.rs b/src/distr/uniform_other.rs index af10eed50d1..f530451aeb0 100644 --- a/src/distr/uniform_other.rs +++ b/src/distr/uniform_other.rs @@ -14,7 +14,7 @@ use crate::distr::Distribution; use crate::Rng; use core::time::Duration; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; impl SampleUniform for char { @@ -31,7 +31,7 @@ impl SampleUniform for char { /// valid Unicode code points. We must therefore avoid sampling values in this /// range. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct UniformChar { sampler: UniformInt, } @@ -118,14 +118,14 @@ impl crate::distr::DistString for Uniform { /// Unless you are implementing [`UniformSampler`] for your own types, this type /// should not be used directly, use [`Uniform`] instead. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct UniformDuration { mode: UniformDurationMode, offset: u32, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] enum UniformDurationMode { Small { secs: u64, @@ -251,7 +251,7 @@ mod tests { use super::*; #[test] - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] fn test_serialization_uniform_duration() { let distr = UniformDuration::new(Duration::from_secs(10), Duration::from_secs(60)).unwrap(); let de_distr: UniformDuration = diff --git a/src/distr/weighted_index.rs b/src/distr/weighted_index.rs index c0ceefee5d1..e7c2669f9f3 100644 --- a/src/distr/weighted_index.rs +++ b/src/distr/weighted_index.rs @@ -17,7 +17,7 @@ use core::fmt; use alloc::vec::Vec; use core::fmt::Debug; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A distribution using weighted sampling of discrete items. @@ -83,7 +83,7 @@ use serde::{Deserialize, Serialize}; /// [`rand_distr::weighted_alias`]: https://docs.rs/rand_distr/*/rand_distr/weighted_alias/index.html /// [`rand_distr::weighted_tree`]: https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html #[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct WeightedIndex { cumulative_weights: Vec, total_weight: X, @@ -437,9 +437,9 @@ impl_weight_float!(f64); mod test { use super::*; - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] #[test] - fn test_weightedindex_serde1() { + fn test_weightedindex_serde() { let weighted_index = WeightedIndex::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).unwrap(); let ser_weighted_index = bincode::serialize(&weighted_index).unwrap(); diff --git a/src/rngs/mock.rs b/src/rngs/mock.rs index 6218626db9b..7238fbc8819 100644 --- a/src/rngs/mock.rs +++ b/src/rngs/mock.rs @@ -10,7 +10,7 @@ use rand_core::{impls, RngCore}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A mock generator yielding very predictable output @@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize}; /// assert_eq!(sample, [2, 3, 4]); /// ``` #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct StepRng { v: u64, a: u64, @@ -79,11 +79,11 @@ rand_core::impl_try_rng_from_rng_core!(StepRng); #[cfg(test)] mod tests { - #[cfg(any(feature = "alloc", feature = "serde1"))] + #[cfg(any(feature = "alloc", feature = "serde"))] use super::StepRng; #[test] - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] fn test_serialization_step_rng() { let some_rng = StepRng::new(42, 7); let de_some_rng: StepRng = diff --git a/src/rngs/xoshiro128plusplus.rs b/src/rngs/xoshiro128plusplus.rs index aa621950164..44e4222c888 100644 --- a/src/rngs/xoshiro128plusplus.rs +++ b/src/rngs/xoshiro128plusplus.rs @@ -9,7 +9,7 @@ use rand_core::impls::{fill_bytes_via_next, next_u64_via_u32}; use rand_core::le::read_u32_into; use rand_core::{RngCore, SeedableRng}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A xoshiro128++ random number generator. @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; /// reference source code](http://xoshiro.di.unimi.it/xoshiro128plusplus.c) by /// David Blackman and Sebastiano Vigna. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Xoshiro128PlusPlus { s: [u32; 4], } diff --git a/src/rngs/xoshiro256plusplus.rs b/src/rngs/xoshiro256plusplus.rs index d6210df6b39..b356ff510c8 100644 --- a/src/rngs/xoshiro256plusplus.rs +++ b/src/rngs/xoshiro256plusplus.rs @@ -9,7 +9,7 @@ use rand_core::impls::fill_bytes_via_next; use rand_core::le::read_u64_into; use rand_core::{RngCore, SeedableRng}; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// A xoshiro256++ random number generator. @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; /// reference source code](http://xoshiro.di.unimi.it/xoshiro256plusplus.c) by /// David Blackman and Sebastiano Vigna. #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Xoshiro256PlusPlus { s: [u64; 4], } diff --git a/src/seq/index.rs b/src/seq/index.rs index d7d1636570c..53bd622e1c5 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -18,7 +18,7 @@ use crate::distr::{Distribution, Uniform}; use crate::Rng; #[cfg(not(feature = "std"))] use alloc::collections::BTreeSet; -#[cfg(feature = "serde1")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::collections::HashSet; @@ -27,7 +27,7 @@ use std::collections::HashSet; /// /// Multiple internal representations are possible. #[derive(Clone, Debug)] -#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum IndexVec { #[doc(hidden)] U32(Vec), @@ -519,7 +519,7 @@ mod test { use alloc::vec; #[test] - #[cfg(feature = "serde1")] + #[cfg(feature = "serde")] fn test_serialization_index_vec() { let some_index_vec = IndexVec::from(vec![254_usize, 234, 2, 1]); let de_some_index_vec: IndexVec = From 2833c784176ae7e5e478ae03f6c661cce9b770fb Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 28 Jul 2024 10:48:07 +0100 Subject: [PATCH 401/443] Document Rng::fill, random differences --- src/rng.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rng.rs b/src/rng.rs index 9c015eddd41..9cea5fb20c6 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -73,7 +73,8 @@ pub trait Rng: RngCore { /// generated. /// /// For arrays of integers, especially for those with small element types - /// (< 64 bit), it will likely be faster to instead use [`Rng::fill`]. + /// (< 64 bit), it will likely be faster to instead use [`Rng::fill`], + /// though note that generated values will differ. /// /// ``` /// use rand::{thread_rng, Rng}; @@ -223,6 +224,10 @@ pub trait Rng: RngCore { /// Fill any type implementing [`Fill`] with random data /// + /// This method is implemented for types which may be safely reinterpreted + /// as an (aligned) `[u8]` slice then filled with random data. It is often + /// faster than using [`Rng::random`] but not value-equivalent. + /// /// The distribution is expected to be uniform with portable results, but /// this cannot be guaranteed for third-party implementations. /// From 79f1b0ffdbc8605c99ce8348d40ee073a2a7bd0f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 12 Aug 2024 12:16:08 +0100 Subject: [PATCH 402/443] Document known issues: #1378, #1312, #1476. --- CHANGELOG.md | 1 + rand_distr/src/binomial.rs | 13 +++++++++++++ rand_distr/src/poisson.rs | 15 +++++++++++++++ src/distr/weighted_index.rs | 2 ++ src/seq/slice.rs | 6 ++++++ 5 files changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 205cf4c14b0..fca9d4807f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Fix portability of `rand::distributions::Slice` (#1469) - Rename `rand::distributions` to `rand::distr` (#1470) - The `serde1` feature has been renamed `serde` (#1477) +- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index fa061b0333d..885d8b21c3f 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -26,6 +26,10 @@ use rand::Rng; /// /// `f(k) = n!/(k! (n-k)!) p^k (1-p)^(n-k)` for `k >= 0`. /// +/// # Known issues +/// +/// See documentation of [`Binomial::new`]. +/// /// # Plot /// /// The following plot of the binomial distribution illustrates the @@ -54,6 +58,8 @@ pub struct Binomial { /// Error type returned from [`Binomial::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +// Marked non_exhaustive to allow a new error code in the solution to #1378. +#[non_exhaustive] pub enum Error { /// `p < 0` or `nan`. ProbabilityTooSmall, @@ -76,6 +82,13 @@ impl std::error::Error for Error {} impl Binomial { /// Construct a new `Binomial` with the given shape parameters `n` (number /// of trials) and `p` (probability of success). + /// + /// # Known issues + /// + /// Although this method should return an [`Error`] on invalid parameters, + /// some (extreme) parameter combinations are known to return a [`Binomial`] + /// object which panics when [sampled](Distribution::sample). + /// See [#1378](https://github.com/rust-random/rand/issues/1378). pub fn new(n: u64, p: f64) -> Result { if !(p >= 0.0) { return Err(Error::ProbabilityTooSmall); diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index fd631a256fe..09eae374dbd 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -23,6 +23,10 @@ use rand::Rng; /// This distribution has density function: /// `f(k) = λ^k * exp(-λ) / k!` for `k >= 0`. /// +/// # Known issues +/// +/// See documentation of [`Poisson::new`]. +/// /// # Plot /// /// The following plot shows the Poisson distribution with various values of `λ`. @@ -56,6 +60,8 @@ where /// Error type returned from [`Poisson::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] +// Marked non_exhaustive to allow a new error code in the solution to #1312. +#[non_exhaustive] pub enum Error { /// `lambda <= 0` ShapeTooSmall, @@ -82,6 +88,15 @@ where { /// Construct a new `Poisson` with the given shape parameter /// `lambda`. + /// + /// # Known issues + /// + /// Although this method should return an [`Error`] on invalid parameters, + /// some (extreme) values of `lambda` are known to return a [`Poisson`] + /// object which hangs when [sampled](Distribution::sample). + /// Large (less extreme) values of `lambda` may result in successful + /// sampling but with reduced precision. + /// See [#1312](https://github.com/rust-random/rand/issues/1312). pub fn new(lambda: F) -> Result, Error> { if !lambda.is_finite() { return Err(Error::NonFinite); diff --git a/src/distr/weighted_index.rs b/src/distr/weighted_index.rs index e7c2669f9f3..0d8eb7d170b 100644 --- a/src/distr/weighted_index.rs +++ b/src/distr/weighted_index.rs @@ -704,6 +704,8 @@ mod test { /// Errors returned by [`WeightedIndex::new`], [`WeightedIndex::update_weights`] and other weighted distributions #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// Marked non_exhaustive to allow a new error code in the solution to #1476. +#[non_exhaustive] pub enum WeightError { /// The input weight sequence is empty, too long, or wrongly ordered InvalidInput, diff --git a/src/seq/slice.rs b/src/seq/slice.rs index 7c86f00a736..75f304c9220 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -189,6 +189,12 @@ pub trait IndexedRandom: Index { /// if the "nightly" feature is enabled, or `O(length)` space and /// `O(length + amount * log length)` time otherwise. /// + /// # Known issues + /// + /// The algorithm currently used to implement this method loses accuracy + /// when small values are used for weights. + /// See [#1476](https://github.com/rust-random/rand/issues/1476). + /// /// # Example /// /// ``` From 9e030aa2218c7c1e15a1c077ed3c7bfd30d520e8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 6 Sep 2024 09:07:47 +0100 Subject: [PATCH 403/443] Migrate remaining benchmarks to Criterion (#1490) Translate everything still using the old test harness to Criterion. --- .github/workflows/benches.yml | 2 +- CHANGELOG.md | 2 +- benches/Cargo.toml | 27 +- benches/benches/array.rs | 94 +++++ benches/benches/base_distributions.rs | 440 ---------------------- benches/benches/bool.rs | 69 ++++ benches/{src => benches}/distr.rs | 3 +- benches/benches/generators.rs | 257 +++++++------ benches/benches/misc.rs | 183 --------- benches/benches/seq.rs | 130 ------- benches/benches/seq_choose.rs | 155 ++++++++ benches/{src => benches}/shuffle.rs | 39 +- benches/benches/standard.rs | 64 ++++ benches/{src => benches}/uniform.rs | 0 benches/{src => benches}/uniform_float.rs | 0 benches/benches/weighted.rs | 70 ++-- benches/src/seq_choose.rs | 111 ------ src/distr/float.rs | 4 +- src/distr/mod.rs | 2 +- src/distr/other.rs | 2 +- 20 files changed, 621 insertions(+), 1033 deletions(-) create mode 100644 benches/benches/array.rs delete mode 100644 benches/benches/base_distributions.rs create mode 100644 benches/benches/bool.rs rename benches/{src => benches}/distr.rs (99%) delete mode 100644 benches/benches/misc.rs delete mode 100644 benches/benches/seq.rs create mode 100644 benches/benches/seq_choose.rs rename benches/{src => benches}/shuffle.rs (60%) create mode 100644 benches/benches/standard.rs rename benches/{src => benches}/uniform.rs (100%) rename benches/{src => benches}/uniform_float.rs (100%) delete mode 100644 benches/src/seq_choose.rs diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index f0112ec88d4..a862bd7e6c7 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -23,4 +23,4 @@ jobs: - name: Clippy run: cargo clippy --all-targets -- -D warnings - name: Build - run: RUSTFLAGS=-Dwarnings cargo build --all-targets + run: RUSTFLAGS=-Dwarnings cargo test --benches diff --git a/CHANGELOG.md b/CHANGELOG.md index fca9d4807f1..cd2eb46c62f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Add `IndexedRandom::choose_multiple_array`, `index::sample_array` (#1453, #1469) - Bump the MSRV to 1.61.0 - Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) -- Move all benchmarks to new `benches` crate (#1439) +- Move all benchmarks to new `benches` crate (#1439) and migrate to Criterion (#1490) - Annotate panicking methods with `#[track_caller]` (#1442, #1447) - Enable feature `small_rng` by default (#1455) - Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 083512d0bc6..a143bff3c02 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -14,27 +14,42 @@ rand_distr = { path = "../rand_distr" } criterion = "0.5" criterion-cycles-per-byte = "0.6" +[[bench]] +name = "array" +harness = false + +[[bench]] +name = "bool" +harness = false + [[bench]] name = "distr" -path = "src/distr.rs" harness = false [[bench]] -name = "uniform" -path = "src/uniform.rs" +name = "generators" harness = false [[bench]] name = "seq_choose" -path = "src/seq_choose.rs" harness = false [[bench]] name = "shuffle" -path = "src/shuffle.rs" +harness = false + +[[bench]] +name = "standard" +harness = false + +[[bench]] +name = "uniform" harness = false [[bench]] name = "uniform_float" -path = "src/uniform_float.rs" +harness = false + +[[bench]] +name = "weighted" harness = false diff --git a/benches/benches/array.rs b/benches/benches/array.rs new file mode 100644 index 00000000000..c8d99dab493 --- /dev/null +++ b/benches/benches/array.rs @@ -0,0 +1,94 @@ +// Copyright 2018-2023 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Generating/filling arrays and iterators of output + +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::distr::Standard; +use rand::prelude::*; +use rand_pcg::Pcg64Mcg; + +criterion_group!( + name = benches; + config = Criterion::default(); + targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + let mut g = c.benchmark_group("gen_1kb"); + g.throughput(criterion::Throughput::Bytes(1024)); + + g.bench_function("u16_iter_repeat", |b| { + use core::iter; + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + b.iter(|| { + let v: Vec = iter::repeat(()).map(|()| rng.random()).take(512).collect(); + v + }); + }); + + g.bench_function("u16_sample_iter", |b| { + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + b.iter(|| { + let v: Vec = Standard.sample_iter(&mut rng).take(512).collect(); + v + }); + }); + + g.bench_function("u16_gen_array", |b| { + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + b.iter(|| { + let v: [u16; 512] = rng.random(); + v + }); + }); + + g.bench_function("u16_fill", |b| { + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut buf = [0u16; 512]; + b.iter(|| { + rng.fill(&mut buf[..]); + buf + }); + }); + + g.bench_function("u64_iter_repeat", |b| { + use core::iter; + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + b.iter(|| { + let v: Vec = iter::repeat(()).map(|()| rng.random()).take(128).collect(); + v + }); + }); + + g.bench_function("u64_sample_iter", |b| { + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + b.iter(|| { + let v: Vec = Standard.sample_iter(&mut rng).take(128).collect(); + v + }); + }); + + g.bench_function("u64_gen_array", |b| { + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + b.iter(|| { + let v: [u64; 128] = rng.random(); + v + }); + }); + + g.bench_function("u64_fill", |b| { + let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut buf = [0u64; 128]; + b.iter(|| { + rng.fill(&mut buf[..]); + buf + }); + }); +} diff --git a/benches/benches/base_distributions.rs b/benches/benches/base_distributions.rs deleted file mode 100644 index 17202a30350..00000000000 --- a/benches/benches/base_distributions.rs +++ /dev/null @@ -1,440 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![feature(custom_inner_attributes)] -#![feature(test)] - -// Rustfmt splits macro invocations to shorten lines; in this case longer-lines are more readable -#![rustfmt::skip] - -extern crate test; - -const RAND_BENCH_N: u64 = 1000; - -use rand::distr::{Alphanumeric, Open01, OpenClosed01, Standard, Uniform}; -use rand::distr::uniform::{UniformInt, UniformSampler}; -use core::mem::size_of; -use core::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8}; -use core::time::Duration; -use test::{Bencher, black_box}; - -use rand::prelude::*; - -// At this time, distributions are optimised for 64-bit platforms. -use rand_pcg::Pcg64Mcg; - -macro_rules! distr_int { - ($fnn:ident, $ty:ty, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - let distr = $distr; - - b.iter(|| { - let mut accum = 0 as $ty; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum = accum.wrapping_add(x); - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -macro_rules! distr_nz_int { - ($fnn:ident, $tynz:ty, $ty:ty, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - let distr = $distr; - - b.iter(|| { - let mut accum = 0 as $ty; - for _ in 0..RAND_BENCH_N { - let x: $tynz = distr.sample(&mut rng); - accum = accum.wrapping_add(x.get()); - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -macro_rules! distr_float { - ($fnn:ident, $ty:ty, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - let distr = $distr; - - b.iter(|| { - let mut accum = 0.0; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum += x; - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -macro_rules! distr_duration { - ($fnn:ident, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - let distr = $distr; - - b.iter(|| { - let mut accum = Duration::new(0, 0); - for _ in 0..RAND_BENCH_N { - let x: Duration = distr.sample(&mut rng); - accum = accum - .checked_add(x) - .unwrap_or(Duration::new(u64::MAX, 999_999_999)); - } - accum - }); - b.bytes = size_of::() as u64 * RAND_BENCH_N; - } - }; -} - -macro_rules! distr { - ($fnn:ident, $ty:ty, $distr:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - let distr = $distr; - - b.iter(|| { - let mut accum = 0u32; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum = accum.wrapping_add(x as u32); - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -// uniform -distr_int!(distr_uniform_i8, i8, Uniform::new(20i8, 100).unwrap()); -distr_int!(distr_uniform_i16, i16, Uniform::new(-500i16, 2000).unwrap()); -distr_int!(distr_uniform_i32, i32, Uniform::new(-200_000_000i32, 800_000_000).unwrap()); -distr_int!(distr_uniform_i64, i64, Uniform::new(3i64, 123_456_789_123).unwrap()); -distr_int!(distr_uniform_i128, i128, Uniform::new(-123_456_789_123i128, 123_456_789_123_456_789).unwrap()); -distr_int!(distr_uniform_usize16, usize, Uniform::new(0usize, 0xb9d7).unwrap()); -distr_int!(distr_uniform_usize32, usize, Uniform::new(0usize, 0x548c0f43).unwrap()); -#[cfg(target_pointer_width = "64")] -distr_int!(distr_uniform_usize64, usize, Uniform::new(0usize, 0x3a42714f2bf927a8).unwrap()); -distr_int!(distr_uniform_isize, isize, Uniform::new(-1060478432isize, 1858574057).unwrap()); - -distr_float!(distr_uniform_f32, f32, Uniform::new(2.26f32, 2.319).unwrap()); -distr_float!(distr_uniform_f64, f64, Uniform::new(2.26f64, 2.319).unwrap()); - -const LARGE_SEC: u64 = u64::MAX / 1000; - -distr_duration!(distr_uniform_duration_largest, - Uniform::new_inclusive(Duration::new(0, 0), Duration::new(u64::MAX, 999_999_999)).unwrap() -); -distr_duration!(distr_uniform_duration_large, - Uniform::new(Duration::new(0, 0), Duration::new(LARGE_SEC, 1_000_000_000 / 2)).unwrap() -); -distr_duration!(distr_uniform_duration_one, - Uniform::new(Duration::new(0, 0), Duration::new(1, 0)).unwrap() -); -distr_duration!(distr_uniform_duration_variety, - Uniform::new(Duration::new(10000, 423423), Duration::new(200000, 6969954)).unwrap() -); -distr_duration!(distr_uniform_duration_edge, - Uniform::new_inclusive(Duration::new(LARGE_SEC, 999_999_999), Duration::new(LARGE_SEC + 1, 1)).unwrap() -); - -// standard -distr_int!(distr_standard_i8, i8, Standard); -distr_int!(distr_standard_i16, i16, Standard); -distr_int!(distr_standard_i32, i32, Standard); -distr_int!(distr_standard_i64, i64, Standard); -distr_int!(distr_standard_i128, i128, Standard); -distr_nz_int!(distr_standard_nz8, NonZeroU8, u8, Standard); -distr_nz_int!(distr_standard_nz16, NonZeroU16, u16, Standard); -distr_nz_int!(distr_standard_nz32, NonZeroU32, u32, Standard); -distr_nz_int!(distr_standard_nz64, NonZeroU64, u64, Standard); -distr_nz_int!(distr_standard_nz128, NonZeroU128, u128, Standard); - -distr!(distr_standard_bool, bool, Standard); -distr!(distr_standard_alphanumeric, u8, Alphanumeric); -distr!(distr_standard_codepoint, char, Standard); - -distr_float!(distr_standard_f32, f32, Standard); -distr_float!(distr_standard_f64, f64, Standard); -distr_float!(distr_open01_f32, f32, Open01); -distr_float!(distr_open01_f64, f64, Open01); -distr_float!(distr_openclosed01_f32, f32, OpenClosed01); -distr_float!(distr_openclosed01_f64, f64, OpenClosed01); - -// construct and sample from a range -macro_rules! gen_range_int { - ($fnn:ident, $ty:ident, $low:expr, $high:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - - b.iter(|| { - let mut high = $high; - let mut accum: $ty = 0; - for _ in 0..RAND_BENCH_N { - accum = accum.wrapping_add(rng.gen_range($low..high)); - // force recalculation of range each time - high = high.wrapping_add(1) & $ty::MAX; - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -// Algorithms such as Fisher–Yates shuffle often require uniform values from an -// incrementing range 0..n. We use -1..n here to prevent wrapping in the test -// from generating a 0-sized range. -gen_range_int!(gen_range_i8_low, i8, -1i8, 0); -gen_range_int!(gen_range_i16_low, i16, -1i16, 0); -gen_range_int!(gen_range_i32_low, i32, -1i32, 0); -gen_range_int!(gen_range_i64_low, i64, -1i64, 0); -gen_range_int!(gen_range_i128_low, i128, -1i128, 0); - -// These were the initially tested ranges. They are likely to see fewer -// rejections than the low tests. -gen_range_int!(gen_range_i8_high, i8, -20i8, 100); -gen_range_int!(gen_range_i16_high, i16, -500i16, 2000); -gen_range_int!(gen_range_i32_high, i32, -200_000_000i32, 800_000_000); -gen_range_int!(gen_range_i64_high, i64, 3i64, 123_456_789_123); -gen_range_int!(gen_range_i128_high, i128, -12345678901234i128, 123_456_789_123_456_789); - -// construct and sample from a floating-point range -macro_rules! gen_range_float { - ($fnn:ident, $ty:ident, $low:expr, $high:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - - b.iter(|| { - let mut high = $high; - let mut low = $low; - let mut accum: $ty = 0.0; - for _ in 0..RAND_BENCH_N { - accum += rng.gen_range(low..high); - // force recalculation of range each time - low += 0.9; - high += 1.1; - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; -} - -gen_range_float!(gen_range_f32, f32, -20000.0f32, 100000.0); -gen_range_float!(gen_range_f64, f64, 123.456f64, 7890.12); - - -// In src/distr/uniform.rs, we say: -// Implementation of [`uniform_single`] is optional, and is only useful when -// the implementation can be faster than `Self::new(low, high).sample(rng)`. - -// `UniformSampler::uniform_single` compromises on the rejection range to be -// faster. This benchmark demonstrates both the speed gain of doing this, and -// the worst case behavior. - -/// Sample random values from a pre-existing distribution. This uses the -/// half open `new` to be equivalent to the behavior of `uniform_single`. -macro_rules! uniform_sample { - ($fnn:ident, $type:ident, $low:expr, $high:expr, $count:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - let low = black_box($low); - let high = black_box($high); - b.iter(|| { - for _ in 0..10 { - let dist = UniformInt::<$type>::new(low, high).unwrap(); - for _ in 0..$count { - black_box(dist.sample(&mut rng)); - } - } - }); - } - }; -} - -macro_rules! uniform_inclusive { - ($fnn:ident, $type:ident, $low:expr, $high:expr, $count:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - let low = black_box($low); - let high = black_box($high); - b.iter(|| { - for _ in 0..10 { - let dist = UniformInt::<$type>::new_inclusive(low, high).unwrap(); - for _ in 0..$count { - black_box(dist.sample(&mut rng)); - } - } - }); - } - }; -} - -/// Use `uniform_single` to create a one-off random value -macro_rules! uniform_single { - ($fnn:ident, $type:ident, $low:expr, $high:expr, $count:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_os_rng(); - let low = black_box($low); - let high = black_box($high); - b.iter(|| { - for _ in 0..(10 * $count) { - black_box(UniformInt::<$type>::sample_single(low, high, &mut rng).unwrap()); - } - }); - } - }; -} - - -// Benchmark: -// n: can use the full generated range -// (n-1): only the max value is rejected: expect this to be fast -// n/2+1: almost half of the values are rejected, and we can do no better -// n/2: approximation rejects half the values but powers of 2 could have no rejection -// n/2-1: only a few values are rejected: expect this to be fast -// 6: approximation rejects 25% of values but could be faster. However modulo by -// low numbers is typically more expensive - -// With the use of u32 as the minimum generated width, the worst-case u16 range -// (32769) will only reject 32769 / 4294967296 samples. -const HALF_16_BIT_UNSIGNED: u16 = 1 << 15; - -uniform_sample!(uniform_u16x1_allm1_new, u16, 0, u16::MAX, 1); -uniform_sample!(uniform_u16x1_halfp1_new, u16, 0, HALF_16_BIT_UNSIGNED + 1, 1); -uniform_sample!(uniform_u16x1_half_new, u16, 0, HALF_16_BIT_UNSIGNED, 1); -uniform_sample!(uniform_u16x1_halfm1_new, u16, 0, HALF_16_BIT_UNSIGNED - 1, 1); -uniform_sample!(uniform_u16x1_6_new, u16, 0, 6u16, 1); - -uniform_single!(uniform_u16x1_allm1_single, u16, 0, u16::MAX, 1); -uniform_single!(uniform_u16x1_halfp1_single, u16, 0, HALF_16_BIT_UNSIGNED + 1, 1); -uniform_single!(uniform_u16x1_half_single, u16, 0, HALF_16_BIT_UNSIGNED, 1); -uniform_single!(uniform_u16x1_halfm1_single, u16, 0, HALF_16_BIT_UNSIGNED - 1, 1); -uniform_single!(uniform_u16x1_6_single, u16, 0, 6u16, 1); - -uniform_inclusive!(uniform_u16x10_all_new_inclusive, u16, 0, u16::MAX, 10); -uniform_sample!(uniform_u16x10_allm1_new, u16, 0, u16::MAX, 10); -uniform_sample!(uniform_u16x10_halfp1_new, u16, 0, HALF_16_BIT_UNSIGNED + 1, 10); -uniform_sample!(uniform_u16x10_half_new, u16, 0, HALF_16_BIT_UNSIGNED, 10); -uniform_sample!(uniform_u16x10_halfm1_new, u16, 0, HALF_16_BIT_UNSIGNED - 1, 10); -uniform_sample!(uniform_u16x10_6_new, u16, 0, 6u16, 10); - -uniform_single!(uniform_u16x10_allm1_single, u16, 0, u16::MAX, 10); -uniform_single!(uniform_u16x10_halfp1_single, u16, 0, HALF_16_BIT_UNSIGNED + 1, 10); -uniform_single!(uniform_u16x10_half_single, u16, 0, HALF_16_BIT_UNSIGNED, 10); -uniform_single!(uniform_u16x10_halfm1_single, u16, 0, HALF_16_BIT_UNSIGNED - 1, 10); -uniform_single!(uniform_u16x10_6_single, u16, 0, 6u16, 10); - - -const HALF_32_BIT_UNSIGNED: u32 = 1 << 31; - -uniform_sample!(uniform_u32x1_allm1_new, u32, 0, u32::MAX, 1); -uniform_sample!(uniform_u32x1_halfp1_new, u32, 0, HALF_32_BIT_UNSIGNED + 1, 1); -uniform_sample!(uniform_u32x1_half_new, u32, 0, HALF_32_BIT_UNSIGNED, 1); -uniform_sample!(uniform_u32x1_halfm1_new, u32, 0, HALF_32_BIT_UNSIGNED - 1, 1); -uniform_sample!(uniform_u32x1_6_new, u32, 0, 6u32, 1); - -uniform_single!(uniform_u32x1_allm1_single, u32, 0, u32::MAX, 1); -uniform_single!(uniform_u32x1_halfp1_single, u32, 0, HALF_32_BIT_UNSIGNED + 1, 1); -uniform_single!(uniform_u32x1_half_single, u32, 0, HALF_32_BIT_UNSIGNED, 1); -uniform_single!(uniform_u32x1_halfm1_single, u32, 0, HALF_32_BIT_UNSIGNED - 1, 1); -uniform_single!(uniform_u32x1_6_single, u32, 0, 6u32, 1); - -uniform_inclusive!(uniform_u32x10_all_new_inclusive, u32, 0, u32::MAX, 10); -uniform_sample!(uniform_u32x10_allm1_new, u32, 0, u32::MAX, 10); -uniform_sample!(uniform_u32x10_halfp1_new, u32, 0, HALF_32_BIT_UNSIGNED + 1, 10); -uniform_sample!(uniform_u32x10_half_new, u32, 0, HALF_32_BIT_UNSIGNED, 10); -uniform_sample!(uniform_u32x10_halfm1_new, u32, 0, HALF_32_BIT_UNSIGNED - 1, 10); -uniform_sample!(uniform_u32x10_6_new, u32, 0, 6u32, 10); - -uniform_single!(uniform_u32x10_allm1_single, u32, 0, u32::MAX, 10); -uniform_single!(uniform_u32x10_halfp1_single, u32, 0, HALF_32_BIT_UNSIGNED + 1, 10); -uniform_single!(uniform_u32x10_half_single, u32, 0, HALF_32_BIT_UNSIGNED, 10); -uniform_single!(uniform_u32x10_halfm1_single, u32, 0, HALF_32_BIT_UNSIGNED - 1, 10); -uniform_single!(uniform_u32x10_6_single, u32, 0, 6u32, 10); - -const HALF_64_BIT_UNSIGNED: u64 = 1 << 63; - -uniform_sample!(uniform_u64x1_allm1_new, u64, 0, u64::MAX, 1); -uniform_sample!(uniform_u64x1_halfp1_new, u64, 0, HALF_64_BIT_UNSIGNED + 1, 1); -uniform_sample!(uniform_u64x1_half_new, u64, 0, HALF_64_BIT_UNSIGNED, 1); -uniform_sample!(uniform_u64x1_halfm1_new, u64, 0, HALF_64_BIT_UNSIGNED - 1, 1); -uniform_sample!(uniform_u64x1_6_new, u64, 0, 6u64, 1); - -uniform_single!(uniform_u64x1_allm1_single, u64, 0, u64::MAX, 1); -uniform_single!(uniform_u64x1_halfp1_single, u64, 0, HALF_64_BIT_UNSIGNED + 1, 1); -uniform_single!(uniform_u64x1_half_single, u64, 0, HALF_64_BIT_UNSIGNED, 1); -uniform_single!(uniform_u64x1_halfm1_single, u64, 0, HALF_64_BIT_UNSIGNED - 1, 1); -uniform_single!(uniform_u64x1_6_single, u64, 0, 6u64, 1); - -uniform_inclusive!(uniform_u64x10_all_new_inclusive, u64, 0, u64::MAX, 10); -uniform_sample!(uniform_u64x10_allm1_new, u64, 0, u64::MAX, 10); -uniform_sample!(uniform_u64x10_halfp1_new, u64, 0, HALF_64_BIT_UNSIGNED + 1, 10); -uniform_sample!(uniform_u64x10_half_new, u64, 0, HALF_64_BIT_UNSIGNED, 10); -uniform_sample!(uniform_u64x10_halfm1_new, u64, 0, HALF_64_BIT_UNSIGNED - 1, 10); -uniform_sample!(uniform_u64x10_6_new, u64, 0, 6u64, 10); - -uniform_single!(uniform_u64x10_allm1_single, u64, 0, u64::MAX, 10); -uniform_single!(uniform_u64x10_halfp1_single, u64, 0, HALF_64_BIT_UNSIGNED + 1, 10); -uniform_single!(uniform_u64x10_half_single, u64, 0, HALF_64_BIT_UNSIGNED, 10); -uniform_single!(uniform_u64x10_halfm1_single, u64, 0, HALF_64_BIT_UNSIGNED - 1, 10); -uniform_single!(uniform_u64x10_6_single, u64, 0, 6u64, 10); - -const HALF_128_BIT_UNSIGNED: u128 = 1 << 127; - -uniform_sample!(uniform_u128x1_allm1_new, u128, 0, u128::MAX, 1); -uniform_sample!(uniform_u128x1_halfp1_new, u128, 0, HALF_128_BIT_UNSIGNED + 1, 1); -uniform_sample!(uniform_u128x1_half_new, u128, 0, HALF_128_BIT_UNSIGNED, 1); -uniform_sample!(uniform_u128x1_halfm1_new, u128, 0, HALF_128_BIT_UNSIGNED - 1, 1); -uniform_sample!(uniform_u128x1_6_new, u128, 0, 6u128, 1); - -uniform_single!(uniform_u128x1_allm1_single, u128, 0, u128::MAX, 1); -uniform_single!(uniform_u128x1_halfp1_single, u128, 0, HALF_128_BIT_UNSIGNED + 1, 1); -uniform_single!(uniform_u128x1_half_single, u128, 0, HALF_128_BIT_UNSIGNED, 1); -uniform_single!(uniform_u128x1_halfm1_single, u128, 0, HALF_128_BIT_UNSIGNED - 1, 1); -uniform_single!(uniform_u128x1_6_single, u128, 0, 6u128, 1); - -uniform_inclusive!(uniform_u128x10_all_new_inclusive, u128, 0, u128::MAX, 10); -uniform_sample!(uniform_u128x10_allm1_new, u128, 0, u128::MAX, 10); -uniform_sample!(uniform_u128x10_halfp1_new, u128, 0, HALF_128_BIT_UNSIGNED + 1, 10); -uniform_sample!(uniform_u128x10_half_new, u128, 0, HALF_128_BIT_UNSIGNED, 10); -uniform_sample!(uniform_u128x10_halfm1_new, u128, 0, HALF_128_BIT_UNSIGNED - 1, 10); -uniform_sample!(uniform_u128x10_6_new, u128, 0, 6u128, 10); - -uniform_single!(uniform_u128x10_allm1_single, u128, 0, u128::MAX, 10); -uniform_single!(uniform_u128x10_halfp1_single, u128, 0, HALF_128_BIT_UNSIGNED + 1, 10); -uniform_single!(uniform_u128x10_half_single, u128, 0, HALF_128_BIT_UNSIGNED, 10); -uniform_single!(uniform_u128x10_halfm1_single, u128, 0, HALF_128_BIT_UNSIGNED - 1, 10); -uniform_single!(uniform_u128x10_6_single, u128, 0, 6u128, 10); diff --git a/benches/benches/bool.rs b/benches/benches/bool.rs new file mode 100644 index 00000000000..e659ce5ba56 --- /dev/null +++ b/benches/benches/bool.rs @@ -0,0 +1,69 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Generating/filling arrays and iterators of output + +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::distr::Bernoulli; +use rand::prelude::*; +use rand_pcg::Pcg32; + +criterion_group!( + name = benches; + config = Criterion::default(); + targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + let mut g = c.benchmark_group("gen_bool"); + g.sample_size(1000); + g.warm_up_time(core::time::Duration::from_millis(500)); + g.measurement_time(core::time::Duration::from_millis(1000)); + + g.bench_function("standard", |b| { + let mut rng = Pcg32::from_rng(&mut thread_rng()); + b.iter(|| rng.sample::(rand::distr::Standard)) + }); + + g.bench_function("const", |b| { + let mut rng = Pcg32::from_rng(&mut thread_rng()); + b.iter(|| rng.gen_bool(0.18)) + }); + + g.bench_function("var", |b| { + let mut rng = Pcg32::from_rng(&mut thread_rng()); + let p = rng.random(); + b.iter(|| rng.gen_bool(p)) + }); + + g.bench_function("ratio_const", |b| { + let mut rng = Pcg32::from_rng(&mut thread_rng()); + b.iter(|| rng.gen_ratio(2, 3)) + }); + + g.bench_function("ratio_var", |b| { + let mut rng = Pcg32::from_rng(&mut thread_rng()); + let d = rng.gen_range(1..=100); + let n = rng.gen_range(0..=d); + b.iter(|| rng.gen_ratio(n, d)); + }); + + g.bench_function("bernoulli_const", |b| { + let mut rng = Pcg32::from_rng(&mut thread_rng()); + let d = Bernoulli::new(0.18).unwrap(); + b.iter(|| rng.sample(d)) + }); + + g.bench_function("bernoulli_var", |b| { + let mut rng = Pcg32::from_rng(&mut thread_rng()); + let p = rng.random(); + let d = Bernoulli::new(p).unwrap(); + b.iter(|| rng.sample(d)) + }); +} diff --git a/benches/src/distr.rs b/benches/benches/distr.rs similarity index 99% rename from benches/src/distr.rs rename to benches/benches/distr.rs index 37b0c8d272b..3097f3a9294 100644 --- a/benches/src/distr.rs +++ b/benches/benches/distr.rs @@ -13,8 +13,7 @@ const RAND_BENCH_N: u64 = 1000; -use criterion::{criterion_group, criterion_main, Criterion, - Throughput}; +use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use criterion_cycles_per_byte::CyclesPerByte; use core::mem::size_of; diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index 4019ec087ec..580d9897551 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -6,139 +6,160 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(test)] -#![allow(non_snake_case)] - -extern crate test; - -const RAND_BENCH_N: u64 = 1000; -const BYTES_LEN: usize = 1024; - -use core::mem::size_of; -use rand_chacha::rand_core::UnwrapErr; -use test::{black_box, Bencher}; - +use core::time::Duration; +use criterion::measurement::WallTime; +use criterion::{black_box, criterion_group, criterion_main, BenchmarkGroup, Criterion}; use rand::prelude::*; use rand::rngs::ReseedingRng; use rand::rngs::{mock::StepRng, OsRng}; +use rand_chacha::rand_core::UnwrapErr; use rand_chacha::{ChaCha12Rng, ChaCha20Core, ChaCha20Rng, ChaCha8Rng}; use rand_pcg::{Pcg32, Pcg64, Pcg64Dxsm, Pcg64Mcg}; -macro_rules! gen_bytes { - ($fnn:ident, $gen:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = $gen; - let mut buf = [0u8; BYTES_LEN]; +criterion_group!( + name = benches; + config = Criterion::default(); + targets = gen_bytes, gen_u32, gen_u64, init_gen, reseeding_bytes +); +criterion_main!(benches); + +pub fn gen_bytes(c: &mut Criterion) { + let mut g = c.benchmark_group("gen_bytes"); + g.warm_up_time(Duration::from_millis(500)); + g.measurement_time(Duration::from_millis(1000)); + g.throughput(criterion::Throughput::Bytes(1024)); + + fn bench(g: &mut BenchmarkGroup, name: &str, mut rng: impl Rng) { + g.bench_function(name, |b| { + let mut buf = [0u8; 1024]; b.iter(|| { - for _ in 0..RAND_BENCH_N { - rng.fill_bytes(&mut buf); - black_box(buf); - } + rng.fill_bytes(&mut buf); + black_box(buf); }); - b.bytes = BYTES_LEN as u64 * RAND_BENCH_N; - } - }; + }); + } + + bench(&mut g, "step", StepRng::new(0, 1)); + bench(&mut g, "pcg32", Pcg32::from_os_rng()); + bench(&mut g, "pcg64", Pcg64::from_os_rng()); + bench(&mut g, "pcg64mcg", Pcg64Mcg::from_os_rng()); + bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_os_rng()); + bench(&mut g, "chacha8", ChaCha8Rng::from_os_rng()); + bench(&mut g, "chacha12", ChaCha12Rng::from_os_rng()); + bench(&mut g, "chacha20", ChaCha20Rng::from_os_rng()); + bench(&mut g, "std", StdRng::from_os_rng()); + bench(&mut g, "small", SmallRng::from_thread_rng()); + bench(&mut g, "os", UnwrapErr(OsRng)); + bench(&mut g, "thread", thread_rng()); + + g.finish() } -gen_bytes!(gen_bytes_step, StepRng::new(0, 1)); -gen_bytes!(gen_bytes_pcg32, Pcg32::from_os_rng()); -gen_bytes!(gen_bytes_pcg64, Pcg64::from_os_rng()); -gen_bytes!(gen_bytes_pcg64mcg, Pcg64Mcg::from_os_rng()); -gen_bytes!(gen_bytes_pcg64dxsm, Pcg64Dxsm::from_os_rng()); -gen_bytes!(gen_bytes_chacha8, ChaCha8Rng::from_os_rng()); -gen_bytes!(gen_bytes_chacha12, ChaCha12Rng::from_os_rng()); -gen_bytes!(gen_bytes_chacha20, ChaCha20Rng::from_os_rng()); -gen_bytes!(gen_bytes_std, StdRng::from_os_rng()); -gen_bytes!(gen_bytes_small, SmallRng::from_thread_rng()); -gen_bytes!(gen_bytes_os, UnwrapErr(OsRng)); -gen_bytes!(gen_bytes_thread, thread_rng()); - -macro_rules! gen_uint { - ($fnn:ident, $ty:ty, $gen:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = $gen; - b.iter(|| { - let mut accum: $ty = 0; - for _ in 0..RAND_BENCH_N { - accum = accum.wrapping_add(rng.random::<$ty>()); - } - accum - }); - b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; - } - }; +pub fn gen_u32(c: &mut Criterion) { + let mut g = c.benchmark_group("gen_u32"); + g.sample_size(1000); + g.warm_up_time(Duration::from_millis(500)); + g.measurement_time(Duration::from_millis(1000)); + g.throughput(criterion::Throughput::Bytes(4)); + + fn bench(g: &mut BenchmarkGroup, name: &str, mut rng: impl Rng) { + g.bench_function(name, |b| { + b.iter(|| rng.random::()); + }); + } + + bench(&mut g, "step", StepRng::new(0, 1)); + bench(&mut g, "pcg32", Pcg32::from_os_rng()); + bench(&mut g, "pcg64", Pcg64::from_os_rng()); + bench(&mut g, "pcg64mcg", Pcg64Mcg::from_os_rng()); + bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_os_rng()); + bench(&mut g, "chacha8", ChaCha8Rng::from_os_rng()); + bench(&mut g, "chacha12", ChaCha12Rng::from_os_rng()); + bench(&mut g, "chacha20", ChaCha20Rng::from_os_rng()); + bench(&mut g, "std", StdRng::from_os_rng()); + bench(&mut g, "small", SmallRng::from_thread_rng()); + bench(&mut g, "os", UnwrapErr(OsRng)); + bench(&mut g, "thread", thread_rng()); + + g.finish() } -gen_uint!(gen_u32_step, u32, StepRng::new(0, 1)); -gen_uint!(gen_u32_pcg32, u32, Pcg32::from_os_rng()); -gen_uint!(gen_u32_pcg64, u32, Pcg64::from_os_rng()); -gen_uint!(gen_u32_pcg64mcg, u32, Pcg64Mcg::from_os_rng()); -gen_uint!(gen_u32_pcg64dxsm, u32, Pcg64Dxsm::from_os_rng()); -gen_uint!(gen_u32_chacha8, u32, ChaCha8Rng::from_os_rng()); -gen_uint!(gen_u32_chacha12, u32, ChaCha12Rng::from_os_rng()); -gen_uint!(gen_u32_chacha20, u32, ChaCha20Rng::from_os_rng()); -gen_uint!(gen_u32_std, u32, StdRng::from_os_rng()); -gen_uint!(gen_u32_small, u32, SmallRng::from_thread_rng()); -gen_uint!(gen_u32_os, u32, UnwrapErr(OsRng)); -gen_uint!(gen_u32_thread, u32, thread_rng()); - -gen_uint!(gen_u64_step, u64, StepRng::new(0, 1)); -gen_uint!(gen_u64_pcg32, u64, Pcg32::from_os_rng()); -gen_uint!(gen_u64_pcg64, u64, Pcg64::from_os_rng()); -gen_uint!(gen_u64_pcg64mcg, u64, Pcg64Mcg::from_os_rng()); -gen_uint!(gen_u64_pcg64dxsm, u64, Pcg64Dxsm::from_os_rng()); -gen_uint!(gen_u64_chacha8, u64, ChaCha8Rng::from_os_rng()); -gen_uint!(gen_u64_chacha12, u64, ChaCha12Rng::from_os_rng()); -gen_uint!(gen_u64_chacha20, u64, ChaCha20Rng::from_os_rng()); -gen_uint!(gen_u64_std, u64, StdRng::from_os_rng()); -gen_uint!(gen_u64_small, u64, SmallRng::from_thread_rng()); -gen_uint!(gen_u64_os, u64, UnwrapErr(OsRng)); -gen_uint!(gen_u64_thread, u64, thread_rng()); - -macro_rules! init_gen { - ($fnn:ident, $gen:ident) => { - #[bench] - fn $fnn(b: &mut Bencher) { +pub fn gen_u64(c: &mut Criterion) { + let mut g = c.benchmark_group("gen_u64"); + g.sample_size(1000); + g.warm_up_time(Duration::from_millis(500)); + g.measurement_time(Duration::from_millis(1000)); + g.throughput(criterion::Throughput::Bytes(8)); + + fn bench(g: &mut BenchmarkGroup, name: &str, mut rng: impl Rng) { + g.bench_function(name, |b| { + b.iter(|| rng.random::()); + }); + } + + bench(&mut g, "step", StepRng::new(0, 1)); + bench(&mut g, "pcg32", Pcg32::from_os_rng()); + bench(&mut g, "pcg64", Pcg64::from_os_rng()); + bench(&mut g, "pcg64mcg", Pcg64Mcg::from_os_rng()); + bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_os_rng()); + bench(&mut g, "chacha8", ChaCha8Rng::from_os_rng()); + bench(&mut g, "chacha12", ChaCha12Rng::from_os_rng()); + bench(&mut g, "chacha20", ChaCha20Rng::from_os_rng()); + bench(&mut g, "std", StdRng::from_os_rng()); + bench(&mut g, "small", SmallRng::from_thread_rng()); + bench(&mut g, "os", UnwrapErr(OsRng)); + bench(&mut g, "thread", thread_rng()); + + g.finish() +} + +pub fn init_gen(c: &mut Criterion) { + let mut g = c.benchmark_group("init_gen"); + g.warm_up_time(Duration::from_millis(500)); + g.measurement_time(Duration::from_millis(1000)); + + fn bench(g: &mut BenchmarkGroup, name: &str) { + g.bench_function(name, |b| { let mut rng = Pcg32::from_os_rng(); - b.iter(|| { - let r2 = $gen::from_rng(&mut rng); - r2 - }); - } - }; + b.iter(|| R::from_rng(&mut rng)); + }); + } + + bench::(&mut g, "pcg32"); + bench::(&mut g, "pcg64"); + bench::(&mut g, "pcg64mcg"); + bench::(&mut g, "pcg64dxsm"); + bench::(&mut g, "chacha8"); + bench::(&mut g, "chacha12"); + bench::(&mut g, "chacha20"); + bench::(&mut g, "std"); + + g.finish() } -init_gen!(init_pcg32, Pcg32); -init_gen!(init_pcg64, Pcg64); -init_gen!(init_pcg64mcg, Pcg64Mcg); -init_gen!(init_pcg64dxsm, Pcg64Dxsm); -init_gen!(init_chacha, ChaCha20Rng); - -const RESEEDING_BYTES_LEN: usize = 1024 * 1024; -const RESEEDING_BENCH_N: u64 = 16; - -macro_rules! reseeding_bytes { - ($fnn:ident, $thresh:expr) => { - #[bench] - fn $fnn(b: &mut Bencher) { - let mut rng = ReseedingRng::new(ChaCha20Core::from_os_rng(), $thresh * 1024, OsRng); - let mut buf = [0u8; RESEEDING_BYTES_LEN]; +pub fn reseeding_bytes(c: &mut Criterion) { + let mut g = c.benchmark_group("reseeding_bytes"); + g.warm_up_time(Duration::from_millis(500)); + g.throughput(criterion::Throughput::Bytes(1024 * 1024)); + + fn bench(g: &mut BenchmarkGroup, thresh: u64) { + let name = format!("chacha20_{}k", thresh); + g.bench_function(name.as_str(), |b| { + let mut rng = ReseedingRng::new(ChaCha20Core::from_os_rng(), thresh * 1024, OsRng); + let mut buf = [0u8; 1024 * 1024]; b.iter(|| { - for _ in 0..RESEEDING_BENCH_N { - rng.fill_bytes(&mut buf); - black_box(&buf); - } + rng.fill_bytes(&mut buf); + black_box(&buf); }); - b.bytes = RESEEDING_BYTES_LEN as u64 * RESEEDING_BENCH_N; - } - }; -} + }); + } + + bench(&mut g, 4); + bench(&mut g, 16); + bench(&mut g, 32); + bench(&mut g, 64); + bench(&mut g, 256); + bench(&mut g, 1024); -reseeding_bytes!(reseeding_chacha20_4k, 4); -reseeding_bytes!(reseeding_chacha20_16k, 16); -reseeding_bytes!(reseeding_chacha20_32k, 32); -reseeding_bytes!(reseeding_chacha20_64k, 64); -reseeding_bytes!(reseeding_chacha20_256k, 256); -reseeding_bytes!(reseeding_chacha20_1M, 1024); + g.finish() +} diff --git a/benches/benches/misc.rs b/benches/benches/misc.rs deleted file mode 100644 index 8a3b4767ef2..00000000000 --- a/benches/benches/misc.rs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![feature(test)] - -extern crate test; - -const RAND_BENCH_N: u64 = 1000; - -use test::Bencher; - -use rand::distr::{Bernoulli, Distribution, Standard}; -use rand::prelude::*; -use rand_pcg::{Pcg32, Pcg64Mcg}; - -#[bench] -fn misc_gen_bool_const(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()); - b.iter(|| { - let mut accum = true; - for _ in 0..RAND_BENCH_N { - accum ^= rng.gen_bool(0.18); - } - accum - }) -} - -#[bench] -fn misc_gen_bool_var(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()); - b.iter(|| { - let mut accum = true; - let mut p = 0.18; - for _ in 0..RAND_BENCH_N { - accum ^= rng.gen_bool(p); - p += 0.0001; - } - accum - }) -} - -#[bench] -fn misc_gen_ratio_const(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()); - b.iter(|| { - let mut accum = true; - for _ in 0..RAND_BENCH_N { - accum ^= rng.gen_ratio(2, 3); - } - accum - }) -} - -#[bench] -fn misc_gen_ratio_var(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()); - b.iter(|| { - let mut accum = true; - for i in 2..(RAND_BENCH_N as u32 + 2) { - accum ^= rng.gen_ratio(i, i + 1); - } - accum - }) -} - -#[bench] -fn misc_bernoulli_const(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()); - b.iter(|| { - let d = Bernoulli::new(0.18).unwrap(); - let mut accum = true; - for _ in 0..RAND_BENCH_N { - accum ^= rng.sample(d); - } - accum - }) -} - -#[bench] -fn misc_bernoulli_var(b: &mut Bencher) { - let mut rng = Pcg32::from_rng(&mut thread_rng()); - b.iter(|| { - let mut accum = true; - let mut p = 0.18; - for _ in 0..RAND_BENCH_N { - let d = Bernoulli::new(p).unwrap(); - accum ^= rng.sample(d); - p += 0.0001; - } - accum - }) -} - -#[bench] -fn gen_1kb_u16_iter_repeat(b: &mut Bencher) { - use core::iter; - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); - b.iter(|| { - let v: Vec = iter::repeat(()).map(|()| rng.random()).take(512).collect(); - v - }); - b.bytes = 1024; -} - -#[bench] -fn gen_1kb_u16_sample_iter(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); - b.iter(|| { - let v: Vec = Standard.sample_iter(&mut rng).take(512).collect(); - v - }); - b.bytes = 1024; -} - -#[bench] -fn gen_1kb_u16_gen_array(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); - b.iter(|| { - // max supported array length is 32! - let v: [[u16; 32]; 16] = rng.random(); - v - }); - b.bytes = 1024; -} - -#[bench] -fn gen_1kb_u16_fill(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); - let mut buf = [0u16; 512]; - b.iter(|| { - rng.fill(&mut buf[..]); - buf - }); - b.bytes = 1024; -} - -#[bench] -fn gen_1kb_u64_iter_repeat(b: &mut Bencher) { - use core::iter; - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); - b.iter(|| { - let v: Vec = iter::repeat(()).map(|()| rng.random()).take(128).collect(); - v - }); - b.bytes = 1024; -} - -#[bench] -fn gen_1kb_u64_sample_iter(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); - b.iter(|| { - let v: Vec = Standard.sample_iter(&mut rng).take(128).collect(); - v - }); - b.bytes = 1024; -} - -#[bench] -fn gen_1kb_u64_gen_array(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); - b.iter(|| { - // max supported array length is 32! - let v: [[u64; 32]; 4] = rng.random(); - v - }); - b.bytes = 1024; -} - -#[bench] -fn gen_1kb_u64_fill(b: &mut Bencher) { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); - let mut buf = [0u64; 128]; - b.iter(|| { - rng.fill(&mut buf[..]); - buf - }); - b.bytes = 1024; -} diff --git a/benches/benches/seq.rs b/benches/benches/seq.rs deleted file mode 100644 index 8b4b774b028..00000000000 --- a/benches/benches/seq.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![feature(test)] -#![allow(non_snake_case)] - -extern crate test; - -use test::Bencher; - -use core::mem::size_of; -use rand::prelude::*; -use rand::seq::*; - -// We force use of 32-bit RNG since seq code is optimised for use with 32-bit -// generators on all platforms. -use rand_pcg::Pcg32 as SmallRng; - -const RAND_BENCH_N: u64 = 1000; - -#[bench] -fn seq_shuffle_100(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()); - let x: &mut [usize] = &mut [1; 100]; - b.iter(|| { - x.shuffle(&mut rng); - x[0] - }) -} - -#[bench] -fn seq_slice_choose_1_of_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()); - let x: &mut [usize] = &mut [1; 1000]; - for (i, r) in x.iter_mut().enumerate() { - *r = i; - } - b.iter(|| { - let mut s = 0; - for _ in 0..RAND_BENCH_N { - s += x.choose(&mut rng).unwrap(); - } - s - }); - b.bytes = size_of::() as u64 * RAND_BENCH_N; -} - -macro_rules! seq_slice_choose_multiple { - ($name:ident, $amount:expr, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()); - let x: &[i32] = &[$amount; $length]; - let mut result = [0i32; $amount]; - b.iter(|| { - // Collect full result to prevent unwanted shortcuts getting - // first element (in case sample_indices returns an iterator). - for (slot, sample) in result.iter_mut().zip(x.choose_multiple(&mut rng, $amount)) { - *slot = *sample; - } - result[$amount - 1] - }) - } - }; -} - -seq_slice_choose_multiple!(seq_slice_choose_multiple_1_of_1000, 1, 1000); -seq_slice_choose_multiple!(seq_slice_choose_multiple_950_of_1000, 950, 1000); -seq_slice_choose_multiple!(seq_slice_choose_multiple_10_of_100, 10, 100); -seq_slice_choose_multiple!(seq_slice_choose_multiple_90_of_100, 90, 100); - -#[bench] -fn seq_iter_choose_multiple_10_of_100(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()); - let x: &[usize] = &[1; 100]; - b.iter(|| x.iter().cloned().choose_multiple(&mut rng, 10)) -} - -#[bench] -fn seq_iter_choose_multiple_fill_10_of_100(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()); - let x: &[usize] = &[1; 100]; - let mut buf = [0; 10]; - b.iter(|| x.iter().cloned().choose_multiple_fill(&mut rng, &mut buf)) -} - -macro_rules! sample_indices { - ($name:ident, $fn:ident, $amount:expr, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()); - b.iter(|| index::$fn(&mut rng, $length, $amount)) - } - }; -} - -sample_indices!(misc_sample_indices_1_of_1k, sample, 1, 1000); -sample_indices!(misc_sample_indices_10_of_1k, sample, 10, 1000); -sample_indices!(misc_sample_indices_100_of_1k, sample, 100, 1000); -sample_indices!(misc_sample_indices_100_of_1M, sample, 100, 1_000_000); -sample_indices!(misc_sample_indices_100_of_1G, sample, 100, 1_000_000_000); -sample_indices!(misc_sample_indices_200_of_1G, sample, 200, 1_000_000_000); -sample_indices!(misc_sample_indices_400_of_1G, sample, 400, 1_000_000_000); -sample_indices!(misc_sample_indices_600_of_1G, sample, 600, 1_000_000_000); - -macro_rules! sample_indices_rand_weights { - ($name:ident, $amount:expr, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()); - b.iter(|| { - index::sample_weighted(&mut rng, $length, |idx| (1 + (idx % 100)) as u32, $amount) - }) - } - }; -} - -sample_indices_rand_weights!(misc_sample_weighted_indices_1_of_1k, 1, 1000); -sample_indices_rand_weights!(misc_sample_weighted_indices_10_of_1k, 10, 1000); -sample_indices_rand_weights!(misc_sample_weighted_indices_100_of_1k, 100, 1000); -sample_indices_rand_weights!(misc_sample_weighted_indices_100_of_1M, 100, 1_000_000); -sample_indices_rand_weights!(misc_sample_weighted_indices_200_of_1M, 200, 1_000_000); -sample_indices_rand_weights!(misc_sample_weighted_indices_400_of_1M, 400, 1_000_000); -sample_indices_rand_weights!(misc_sample_weighted_indices_600_of_1M, 600, 1_000_000); -sample_indices_rand_weights!(misc_sample_weighted_indices_1k_of_1M, 1000, 1_000_000); diff --git a/benches/benches/seq_choose.rs b/benches/benches/seq_choose.rs new file mode 100644 index 00000000000..f418f9cc4dc --- /dev/null +++ b/benches/benches/seq_choose.rs @@ -0,0 +1,155 @@ +// Copyright 2018-2023 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::prelude::*; +use rand::SeedableRng; +use rand_pcg::Pcg32; + +criterion_group!( + name = benches; + config = Criterion::default(); + targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + c.bench_function("seq_slice_choose_1_of_1000", |b| { + let mut rng = Pcg32::from_rng(thread_rng()); + let mut buf = [0i32; 100]; + rng.fill(&mut buf); + let x = black_box(&mut buf); + + b.iter(|| x.choose(&mut rng).unwrap()); + }); + + let lens = [(1, 1000), (950, 1000), (10, 100), (90, 100)]; + for (amount, len) in lens { + let name = format!("seq_slice_choose_multiple_{}_of_{}", amount, len); + c.bench_function(name.as_str(), |b| { + let mut rng = Pcg32::from_rng(thread_rng()); + let mut buf = [0i32; 1000]; + rng.fill(&mut buf); + let x = black_box(&buf[..len]); + + let mut results_buf = [0i32; 950]; + let y = black_box(&mut results_buf[..amount]); + let amount = black_box(amount); + + b.iter(|| { + // Collect full result to prevent unwanted shortcuts getting + // first element (in case sample_indices returns an iterator). + for (slot, sample) in y.iter_mut().zip(x.choose_multiple(&mut rng, amount)) { + *slot = *sample; + } + y[amount - 1] + }) + }); + } + + c.bench_function("seq_iter_choose_multiple_10_of_100", |b| { + let mut rng = Pcg32::from_rng(thread_rng()); + let mut buf = [0i32; 100]; + rng.fill(&mut buf); + let x = black_box(&buf); + b.iter(|| x.iter().cloned().choose_multiple(&mut rng, 10)) + }); + + c.bench_function("seq_iter_choose_multiple_fill_10_of_100", |b| { + let mut rng = Pcg32::from_rng(thread_rng()); + let mut buf = [0i32; 100]; + rng.fill(&mut buf); + let x = black_box(&buf); + let mut buf = [0; 10]; + b.iter(|| x.iter().cloned().choose_multiple_fill(&mut rng, &mut buf)) + }); + + bench_rng::(c, "ChaCha20"); + bench_rng::(c, "Pcg32"); + bench_rng::(c, "Pcg64"); +} + +fn bench_rng(c: &mut Criterion, rng_name: &'static str) { + for length in [1, 2, 3, 10, 100, 1000].map(black_box) { + let name = format!("choose_size-hinted_from_{length}_{rng_name}"); + c.bench_function(name.as_str(), |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_size_hinted(length, &mut rng)) + }); + + let name = format!("choose_stable_from_{length}_{rng_name}"); + c.bench_function(name.as_str(), |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_stable(length, &mut rng)) + }); + + let name = format!("choose_unhinted_from_{length}_{rng_name}"); + c.bench_function(name.as_str(), |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_unhinted(length, &mut rng)) + }); + + let name = format!("choose_windowed_from_{length}_{rng_name}"); + c.bench_function(name.as_str(), |b| { + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_windowed(length, 7, &mut rng)) + }); + } +} + +fn choose_size_hinted(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose(rng) +} + +fn choose_stable(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose_stable(rng) +} + +fn choose_unhinted(max: usize, rng: &mut R) -> Option { + let iterator = UnhintedIterator { iter: (0..max) }; + iterator.choose(rng) +} + +fn choose_windowed(max: usize, window_size: usize, rng: &mut R) -> Option { + let iterator = WindowHintedIterator { + iter: (0..max), + window_size, + }; + iterator.choose(rng) +} + +#[derive(Clone)] +struct UnhintedIterator { + iter: I, +} +impl Iterator for UnhintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +#[derive(Clone)] +struct WindowHintedIterator { + iter: I, + window_size: usize, +} +impl Iterator for WindowHintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + (core::cmp::min(self.iter.len(), self.window_size), None) + } +} diff --git a/benches/src/shuffle.rs b/benches/benches/shuffle.rs similarity index 60% rename from benches/src/shuffle.rs rename to benches/benches/shuffle.rs index 4d6e31fa38c..106d12e0abc 100644 --- a/benches/src/shuffle.rs +++ b/benches/benches/shuffle.rs @@ -5,18 +5,31 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. + use criterion::{black_box, criterion_group, criterion_main, Criterion}; use rand::prelude::*; use rand::SeedableRng; +use rand_pcg::Pcg32; criterion_group!( -name = benches; -config = Criterion::default(); -targets = bench + name = benches; + config = Criterion::default(); + targets = bench ); criterion_main!(benches); pub fn bench(c: &mut Criterion) { + c.bench_function("seq_shuffle_100", |b| { + let mut rng = Pcg32::from_rng(thread_rng()); + let mut buf = [0i32; 100]; + rng.fill(&mut buf); + let x = black_box(&mut buf); + b.iter(|| { + x.shuffle(&mut rng); + x[0] + }) + }); + bench_rng::(c, "ChaCha12"); bench_rng::(c, "Pcg32"); bench_rng::(c, "Pcg64"); @@ -34,17 +47,15 @@ fn bench_rng(c: &mut Criterion, rng_name: &'static s }); if length >= 10 { - c.bench_function( - format!("partial_shuffle_{length}_{rng_name}").as_str(), - |b| { - let mut rng = Rng::seed_from_u64(123); - let mut vec: Vec = (0..length).collect(); - b.iter(|| { - vec.partial_shuffle(&mut rng, length / 2); - vec[0] - }) - }, - ); + let name = format!("partial_shuffle_{length}_{rng_name}"); + c.bench_function(name.as_str(), |b| { + let mut rng = Rng::seed_from_u64(123); + let mut vec: Vec = (0..length).collect(); + b.iter(|| { + vec.partial_shuffle(&mut rng, length / 2); + vec[0] + }) + }); } } } diff --git a/benches/benches/standard.rs b/benches/benches/standard.rs new file mode 100644 index 00000000000..1e7fadc77a2 --- /dev/null +++ b/benches/benches/standard.rs @@ -0,0 +1,64 @@ +// Copyright 2019 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::time::Duration; +use criterion::measurement::WallTime; +use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion}; +use rand::distr::{Alphanumeric, Standard}; +use rand::prelude::*; +use rand_distr::{Open01, OpenClosed01}; +use rand_pcg::Pcg64Mcg; + +criterion_group!( + name = benches; + config = Criterion::default(); + targets = bench +); +criterion_main!(benches); + +fn bench_ty(g: &mut BenchmarkGroup, name: &str) +where + D: Distribution + Default, +{ + g.throughput(criterion::Throughput::Bytes(size_of::() as u64)); + g.bench_function(name, |b| { + let mut rng = Pcg64Mcg::from_os_rng(); + + b.iter(|| rng.sample::(D::default())); + }); +} + +pub fn bench(c: &mut Criterion) { + let mut g = c.benchmark_group("Standard"); + g.sample_size(1000); + g.warm_up_time(Duration::from_millis(500)); + g.measurement_time(Duration::from_millis(1000)); + + macro_rules! do_ty { + ($t:ty) => { + bench_ty::<$t, Standard>(&mut g, stringify!($t)); + }; + ($t:ty, $($tt:ty),*) => { + do_ty!($t); + do_ty!($($tt),*); + }; + } + + do_ty!(i8, i16, i32, i64, i128, isize); + do_ty!(f32, f64); + do_ty!(char); + + bench_ty::(&mut g, "Alphanumeric"); + + bench_ty::(&mut g, "Open01/f32"); + bench_ty::(&mut g, "Open01/f64"); + bench_ty::(&mut g, "OpenClosed01/f32"); + bench_ty::(&mut g, "OpenClosed01/f64"); + + g.finish(); +} diff --git a/benches/src/uniform.rs b/benches/benches/uniform.rs similarity index 100% rename from benches/src/uniform.rs rename to benches/benches/uniform.rs diff --git a/benches/src/uniform_float.rs b/benches/benches/uniform_float.rs similarity index 100% rename from benches/src/uniform_float.rs rename to benches/benches/uniform_float.rs diff --git a/benches/benches/weighted.rs b/benches/benches/weighted.rs index da437ab5b0b..21e66ebee3b 100644 --- a/benches/benches/weighted.rs +++ b/benches/benches/weighted.rs @@ -6,31 +6,55 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(test)] +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::distr::WeightedIndex; +use rand::prelude::*; +use rand::seq::index::sample_weighted; -extern crate test; +criterion_group!( + name = benches; + config = Criterion::default(); + targets = bench +); +criterion_main!(benches); -use rand::distr::WeightedIndex; -use rand::Rng; -use test::Bencher; +pub fn bench(c: &mut Criterion) { + c.bench_function("weighted_index_creation", |b| { + let mut rng = rand::thread_rng(); + let weights = black_box([1u32, 2, 4, 0, 5, 1, 7, 1, 2, 3, 4, 5, 6, 7]); + b.iter(|| { + let distr = WeightedIndex::new(weights.to_vec()).unwrap(); + rng.sample(distr) + }) + }); -#[bench] -fn weighted_index_creation(b: &mut Bencher) { - let mut rng = rand::thread_rng(); - let weights = [1u32, 2, 4, 0, 5, 1, 7, 1, 2, 3, 4, 5, 6, 7]; - b.iter(|| { - let distr = WeightedIndex::new(weights.to_vec()).unwrap(); - rng.sample(distr) - }) -} + c.bench_function("weighted_index_modification", |b| { + let mut rng = rand::thread_rng(); + let weights = black_box([1u32, 2, 3, 0, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]); + let mut distr = WeightedIndex::new(weights.to_vec()).unwrap(); + b.iter(|| { + distr.update_weights(&[(2, &4), (5, &1)]).unwrap(); + rng.sample(&distr) + }) + }); -#[bench] -fn weighted_index_modification(b: &mut Bencher) { - let mut rng = rand::thread_rng(); - let weights = [1u32, 2, 3, 0, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]; - let mut distr = WeightedIndex::new(weights.to_vec()).unwrap(); - b.iter(|| { - distr.update_weights(&[(2, &4), (5, &1)]).unwrap(); - rng.sample(&distr) - }) + let lens = [ + (1, 1000, "1k"), + (10, 1000, "1k"), + (100, 1000, "1k"), + (100, 1_000_000, "1M"), + (200, 1_000_000, "1M"), + (400, 1_000_000, "1M"), + (600, 1_000_000, "1M"), + (1000, 1_000_000, "1M"), + ]; + for (amount, length, len_name) in lens { + let name = format!("weighted_sample_indices_{}_of_{}", amount, len_name); + c.bench_function(name.as_str(), |b| { + let length = black_box(length); + let amount = black_box(amount); + let mut rng = SmallRng::from_rng(thread_rng()); + b.iter(|| sample_weighted(&mut rng, length, |idx| (1 + (idx % 100)) as u32, amount)) + }); + } } diff --git a/benches/src/seq_choose.rs b/benches/src/seq_choose.rs deleted file mode 100644 index ccf7e5825aa..00000000000 --- a/benches/src/seq_choose.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2018-2023 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use rand::prelude::*; -use rand::SeedableRng; - -criterion_group!( -name = benches; -config = Criterion::default(); -targets = bench -); -criterion_main!(benches); - -pub fn bench(c: &mut Criterion) { - bench_rng::(c, "ChaCha20"); - bench_rng::(c, "Pcg32"); - bench_rng::(c, "Pcg64"); -} - -fn bench_rng(c: &mut Criterion, rng_name: &'static str) { - for length in [1, 2, 3, 10, 100, 1000].map(black_box) { - c.bench_function( - format!("choose_size-hinted_from_{length}_{rng_name}").as_str(), - |b| { - let mut rng = Rng::seed_from_u64(123); - b.iter(|| choose_size_hinted(length, &mut rng)) - }, - ); - - c.bench_function( - format!("choose_stable_from_{length}_{rng_name}").as_str(), - |b| { - let mut rng = Rng::seed_from_u64(123); - b.iter(|| choose_stable(length, &mut rng)) - }, - ); - - c.bench_function( - format!("choose_unhinted_from_{length}_{rng_name}").as_str(), - |b| { - let mut rng = Rng::seed_from_u64(123); - b.iter(|| choose_unhinted(length, &mut rng)) - }, - ); - - c.bench_function( - format!("choose_windowed_from_{length}_{rng_name}").as_str(), - |b| { - let mut rng = Rng::seed_from_u64(123); - b.iter(|| choose_windowed(length, 7, &mut rng)) - }, - ); - } -} - -fn choose_size_hinted(max: usize, rng: &mut R) -> Option { - let iterator = 0..max; - iterator.choose(rng) -} - -fn choose_stable(max: usize, rng: &mut R) -> Option { - let iterator = 0..max; - iterator.choose_stable(rng) -} - -fn choose_unhinted(max: usize, rng: &mut R) -> Option { - let iterator = UnhintedIterator { iter: (0..max) }; - iterator.choose(rng) -} - -fn choose_windowed(max: usize, window_size: usize, rng: &mut R) -> Option { - let iterator = WindowHintedIterator { - iter: (0..max), - window_size, - }; - iterator.choose(rng) -} - -#[derive(Clone)] -struct UnhintedIterator { - iter: I, -} -impl Iterator for UnhintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } -} - -#[derive(Clone)] -struct WindowHintedIterator { - iter: I, - window_size: usize, -} -impl Iterator for WindowHintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - (core::cmp::min(self.iter.len(), self.window_size), None) - } -} diff --git a/src/distr/float.rs b/src/distr/float.rs index 0732b0afe55..a8cbc96bd6f 100644 --- a/src/distr/float.rs +++ b/src/distr/float.rs @@ -42,7 +42,7 @@ use serde::{Deserialize, Serialize}; /// [`Standard`]: crate::distr::Standard /// [`Open01`]: crate::distr::Open01 /// [`Uniform`]: crate::distr::uniform::Uniform -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct OpenClosed01; @@ -69,7 +69,7 @@ pub struct OpenClosed01; /// [`Standard`]: crate::distr::Standard /// [`OpenClosed01`]: crate::distr::OpenClosed01 /// [`Uniform`]: crate::distr::uniform::Uniform -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Open01; diff --git a/src/distr/mod.rs b/src/distr/mod.rs index 668c9ffda7f..716aa417e3a 100644 --- a/src/distr/mod.rs +++ b/src/distr/mod.rs @@ -217,6 +217,6 @@ use crate::Rng; /// [`f32x4`]: std::simd::f32x4 /// [`mask32x4`]: std::simd::mask32x4 /// [`simd_support`]: https://github.com/rust-random/rand#crate-features -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Standard; diff --git a/src/distr/other.rs b/src/distr/other.rs index ef10828d1c6..b2e91e53256 100644 --- a/src/distr/other.rs +++ b/src/distr/other.rs @@ -66,7 +66,7 @@ use serde::{Deserialize, Serialize}; /// /// - [Wikipedia article on Password Strength](https://en.wikipedia.org/wiki/Password_strength) /// - [Diceware for generating memorable passwords](https://en.wikipedia.org/wiki/Diceware) -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Alphanumeric; From 71c53be9a946415c7499a8d9d42480b406d706ee Mon Sep 17 00:00:00 2001 From: Clar Fon <15850505+clarfonthey@users.noreply.github.com> Date: Mon, 9 Sep 2024 03:10:01 -0400 Subject: [PATCH 404/443] Require SeedableRng::Seed to impl Clone + AsRef (#1491) --- CHANGELOG.md | 1 + rand_core/src/lib.rs | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd2eb46c62f..36aaa167828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `rand::distributions` to `rand::distr` (#1470) - The `serde1` feature has been renamed `serde` (#1477) - Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). +- Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 275cedc7418..3c16a9767c9 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -279,16 +279,15 @@ pub trait SeedableRng: Sized { /// /// # Implementing `SeedableRng` for RNGs with large seeds /// - /// Note that the required traits `core::default::Default` and - /// `core::convert::AsMut` are not implemented for large arrays - /// `[u8; N]` with `N` > 32. To be able to implement the traits required by - /// `SeedableRng` for RNGs with such large seeds, the newtype pattern can be - /// used: + /// Note that [`Default`] is not implemented for large arrays `[u8; N]` with + /// `N` > 32. To be able to implement the traits required by `SeedableRng` + /// for RNGs with such large seeds, the newtype pattern can be used: /// /// ``` /// use rand_core::SeedableRng; /// /// const N: usize = 64; + /// #[derive(Clone)] /// pub struct MyRngSeed(pub [u8; N]); /// # #[allow(dead_code)] /// pub struct MyRng(MyRngSeed); @@ -299,6 +298,12 @@ pub trait SeedableRng: Sized { /// } /// } /// + /// impl AsRef<[u8]> for MyRngSeed { + /// fn as_ref(&self) -> &[u8] { + /// &self.0 + /// } + /// } + /// /// impl AsMut<[u8]> for MyRngSeed { /// fn as_mut(&mut self) -> &mut [u8] { /// &mut self.0 @@ -313,7 +318,7 @@ pub trait SeedableRng: Sized { /// } /// } /// ``` - type Seed: Sized + Default + AsMut<[u8]>; + type Seed: Clone + Default + AsRef<[u8]> + AsMut<[u8]>; /// Create a new PRNG using the given seed. /// From ef052ec539ed13dfbe5938559ca78f92071873f4 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 9 Sep 2024 08:33:43 +0100 Subject: [PATCH 405/443] No usize except uniform (#1487) - Add UniformUsize - Support ..end and ..=ub range syntax for unsigned ints --- CHANGELOG.md | 2 + benches/benches/standard.rs | 2 +- rand_distr/src/weighted_alias.rs | 1 - src/distr/integer.rs | 36 +--- src/distr/slice.rs | 35 +--- src/distr/uniform.rs | 45 ++++- src/distr/uniform_int.rs | 276 ++++++++++++++++++++++++++++++- src/lib.rs | 4 +- src/rng.rs | 13 +- src/seq/index.rs | 134 +++++++++------ src/seq/iterator.rs | 9 +- src/seq/mod.rs | 17 +- src/seq/slice.rs | 10 +- 13 files changed, 416 insertions(+), 168 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36aaa167828..d071e093916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `rand::distributions` to `rand::distr` (#1470) - The `serde1` feature has been renamed `serde` (#1477) - Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). +- Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) +- Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/benches/benches/standard.rs b/benches/benches/standard.rs index 1e7fadc77a2..cd88ea66a28 100644 --- a/benches/benches/standard.rs +++ b/benches/benches/standard.rs @@ -49,7 +49,7 @@ pub fn bench(c: &mut Criterion) { }; } - do_ty!(i8, i16, i32, i64, i128, isize); + do_ty!(i8, i16, i32, i64, i128); do_ty!(f32, f64); do_ty!(char); diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 8826b5b76c5..593219cafdd 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -365,7 +365,6 @@ impl_weight_for_int!(u64); impl_weight_for_int!(u32); impl_weight_for_int!(u16); impl_weight_for_int!(u8); -impl_weight_for_int!(isize); impl_weight_for_int!(i128); impl_weight_for_int!(i64); impl_weight_for_int!(i32); diff --git a/src/distr/integer.rs b/src/distr/integer.rs index 49546a39417..8cf9ffc6886 100644 --- a/src/distr/integer.rs +++ b/src/distr/integer.rs @@ -19,8 +19,8 @@ use core::arch::x86_64::__m512i; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::{__m128i, __m256i}; use core::num::{ - NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, - NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, + NonZeroU32, NonZeroU64, NonZeroU8, }; #[cfg(feature = "simd_support")] use core::simd::*; @@ -63,20 +63,6 @@ impl Distribution for Standard { } } -impl Distribution for Standard { - #[inline] - #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] - fn sample(&self, rng: &mut R) -> usize { - rng.next_u32() as usize - } - - #[inline] - #[cfg(target_pointer_width = "64")] - fn sample(&self, rng: &mut R) -> usize { - rng.next_u64() as usize - } -} - macro_rules! impl_int_from_uint { ($ty:ty, $uty:ty) => { impl Distribution<$ty> for Standard { @@ -93,7 +79,6 @@ impl_int_from_uint! { i16, u16 } impl_int_from_uint! { i32, u32 } impl_int_from_uint! { i64, u64 } impl_int_from_uint! { i128, u128 } -impl_int_from_uint! { isize, usize } macro_rules! impl_nzint { ($ty:ty, $new:path) => { @@ -114,14 +99,12 @@ impl_nzint!(NonZeroU16, NonZeroU16::new); impl_nzint!(NonZeroU32, NonZeroU32::new); impl_nzint!(NonZeroU64, NonZeroU64::new); impl_nzint!(NonZeroU128, NonZeroU128::new); -impl_nzint!(NonZeroUsize, NonZeroUsize::new); impl_nzint!(NonZeroI8, NonZeroI8::new); impl_nzint!(NonZeroI16, NonZeroI16::new); impl_nzint!(NonZeroI32, NonZeroI32::new); impl_nzint!(NonZeroI64, NonZeroI64::new); impl_nzint!(NonZeroI128, NonZeroI128::new); -impl_nzint!(NonZeroIsize, NonZeroIsize::new); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] macro_rules! x86_intrinsic_impl { @@ -163,7 +146,7 @@ macro_rules! simd_impl { } #[cfg(feature = "simd_support")] -simd_impl!(u8, i8, u16, i16, u32, i32, u64, i64, usize, isize); +simd_impl!(u8, i8, u16, i16, u32, i32, u64, i64); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] x86_intrinsic_impl!( @@ -191,14 +174,12 @@ mod tests { fn test_integers() { let mut rng = crate::test::rng(806); - rng.sample::(Standard); rng.sample::(Standard); rng.sample::(Standard); rng.sample::(Standard); rng.sample::(Standard); rng.sample::(Standard); - rng.sample::(Standard); rng.sample::(Standard); rng.sample::(Standard); rng.sample::(Standard); @@ -239,17 +220,6 @@ mod tests { 111087889832015897993126088499035356354, ], ); - #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))] - test_samples(0usize, &[2220326409, 2575017975, 2018088303]); - #[cfg(target_pointer_width = "64")] - test_samples( - 0usize, - &[ - 11059617991457472009, - 16096616328739788143, - 1487364411147516184, - ], - ); test_samples(0i8, &[9, -9, 111]); // Skip further i* types: they are simple reinterpretation of u* samples diff --git a/src/distr/slice.rs b/src/distr/slice.rs index 4677b4e89e4..d201caa2463 100644 --- a/src/distr/slice.rs +++ b/src/distr/slice.rs @@ -8,40 +8,11 @@ use core::num::NonZeroUsize; -use crate::distr::{Distribution, Uniform}; -use crate::Rng; +use crate::distr::uniform::{UniformSampler, UniformUsize}; +use crate::distr::Distribution; #[cfg(feature = "alloc")] use alloc::string::String; -#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] -compile_error!("unsupported pointer width"); - -#[derive(Debug, Clone, Copy)] -enum UniformUsize { - U32(Uniform), - #[cfg(target_pointer_width = "64")] - U64(Uniform), -} - -impl UniformUsize { - pub fn new(ubound: usize) -> Result { - #[cfg(target_pointer_width = "64")] - if ubound > (u32::MAX as usize) { - return Uniform::new(0, ubound as u64).map(UniformUsize::U64); - } - - Uniform::new(0, ubound as u32).map(UniformUsize::U32) - } - - pub fn sample(&self, rng: &mut R) -> usize { - match self { - UniformUsize::U32(uu) => uu.sample(rng) as usize, - #[cfg(target_pointer_width = "64")] - UniformUsize::U64(uu) => uu.sample(rng) as usize, - } - } -} - /// A distribution to sample items uniformly from a slice. /// /// [`Slice::new`] constructs a distribution referencing a slice and uniformly @@ -110,7 +81,7 @@ impl<'a, T> Slice<'a, T> { Ok(Self { slice, - range: UniformUsize::new(num_choices.get()).unwrap(), + range: UniformUsize::new(0, num_choices.get()).unwrap(), num_choices, }) } diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 4f07aaf26eb..86a08fdc59f 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -112,7 +112,7 @@ pub use float::UniformFloat; #[path = "uniform_int.rs"] mod int; #[doc(inline)] -pub use int::UniformInt; +pub use int::{UniformInt, UniformUsize}; #[path = "uniform_other.rs"] mod other; @@ -120,7 +120,7 @@ mod other; pub use other::{UniformChar, UniformDuration}; use core::fmt; -use core::ops::{Range, RangeInclusive}; +use core::ops::{Range, RangeInclusive, RangeTo, RangeToInclusive}; use crate::distr::Distribution; use crate::{Rng, RngCore}; @@ -439,6 +439,41 @@ impl SampleRange for RangeInclusive { } } +macro_rules! impl_sample_range_u { + ($t:ty) => { + impl SampleRange<$t> for RangeTo<$t> { + #[inline] + fn sample_single(self, rng: &mut R) -> Result<$t, Error> { + <$t as SampleUniform>::Sampler::sample_single(0, self.end, rng) + } + + #[inline] + fn is_empty(&self) -> bool { + 0 == self.end + } + } + + impl SampleRange<$t> for RangeToInclusive<$t> { + #[inline] + fn sample_single(self, rng: &mut R) -> Result<$t, Error> { + <$t as SampleUniform>::Sampler::sample_single_inclusive(0, self.end, rng) + } + + #[inline] + fn is_empty(&self) -> bool { + false + } + } + }; +} + +impl_sample_range_u!(u8); +impl_sample_range_u!(u16); +impl_sample_range_u!(u32); +impl_sample_range_u!(u64); +impl_sample_range_u!(u128); +impl_sample_range_u!(usize); + #[cfg(test)] mod tests { use super::*; @@ -530,12 +565,6 @@ mod tests { assert_eq!(&buf, expected_multiple); } - // We test on a sub-set of types; possibly we should do more. - // TODO: SIMD types - - test_samples(11u8, 219, &[17, 66, 214], &[181, 93, 165]); - test_samples(11u32, 219, &[17, 66, 214], &[181, 93, 165]); - test_samples( 0f32, 1e-2f32, diff --git a/src/distr/uniform_int.rs b/src/distr/uniform_int.rs index 4a5bb319910..b53ca367b9f 100644 --- a/src/distr/uniform_int.rs +++ b/src/distr/uniform_int.rs @@ -258,12 +258,10 @@ uniform_int_impl! { i16, u16, u32 } uniform_int_impl! { i32, u32, u32 } uniform_int_impl! { i64, u64, u64 } uniform_int_impl! { i128, u128, u128 } -uniform_int_impl! { isize, usize, usize } uniform_int_impl! { u8, u8, u32 } uniform_int_impl! { u16, u16, u32 } uniform_int_impl! { u32, u32, u32 } uniform_int_impl! { u64, u64, u64 } -uniform_int_impl! { usize, usize, usize } uniform_int_impl! { u128, u128, u128 } #[cfg(feature = "simd_support")] @@ -385,10 +383,189 @@ macro_rules! uniform_simd_int_impl { #[cfg(feature = "simd_support")] uniform_simd_int_impl! { (u8, i8), (u16, i16), (u32, i32), (u64, i64) } +/// The back-end implementing [`UniformSampler`] for `usize`. +/// +/// # Implementation notes +/// +/// Sampling a `usize` value is usually used in relation to the length of an +/// array or other memory structure, thus it is reasonable to assume that the +/// vast majority of use-cases will have a maximum size under [`u32::MAX`]. +/// In part to optimise for this use-case, but mostly to ensure that results +/// are portable across 32-bit and 64-bit architectures (as far as is possible), +/// this implementation will use 32-bit sampling when possible. +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct UniformUsize { + low: usize, + range: usize, + thresh: usize, + #[cfg(target_pointer_width = "64")] + mode64: bool, +} + +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +impl SampleUniform for usize { + type Sampler = UniformUsize; +} + +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +impl UniformSampler for UniformUsize { + type X = usize; + + #[inline] // if the range is constant, this helps LLVM to do the + // calculations at compile-time. + fn new(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low < high) { + return Err(Error::EmptyRange); + } + + UniformSampler::new_inclusive(low, high - 1) + } + + #[inline] // if the range is constant, this helps LLVM to do the + // calculations at compile-time. + fn new_inclusive(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low <= high) { + return Err(Error::EmptyRange); + } + + #[cfg(target_pointer_width = "64")] + let mode64 = high > (u32::MAX as usize); + #[cfg(target_pointer_width = "32")] + let mode64 = false; + + let (range, thresh); + if cfg!(target_pointer_width = "64") && !mode64 { + let range32 = (high as u32).wrapping_sub(low as u32).wrapping_add(1); + range = range32 as usize; + thresh = if range32 > 0 { + (range32.wrapping_neg() % range32) as usize + } else { + 0 + }; + } else { + range = high.wrapping_sub(low).wrapping_add(1); + thresh = if range > 0 { + range.wrapping_neg() % range + } else { + 0 + }; + } + + Ok(UniformUsize { + low, + range, + thresh, + #[cfg(target_pointer_width = "64")] + mode64, + }) + } + + #[inline] + fn sample(&self, rng: &mut R) -> usize { + #[cfg(target_pointer_width = "32")] + let mode32 = true; + #[cfg(target_pointer_width = "64")] + let mode32 = !self.mode64; + + if mode32 { + let range = self.range as u32; + if range == 0 { + return rng.random::() as usize; + } + + let thresh = self.thresh as u32; + let hi = loop { + let (hi, lo) = rng.random::().wmul(range); + if lo >= thresh { + break hi; + } + }; + self.low.wrapping_add(hi as usize) + } else { + let range = self.range as u64; + if range == 0 { + return rng.random::() as usize; + } + + let thresh = self.thresh as u64; + let hi = loop { + let (hi, lo) = rng.random::().wmul(range); + if lo >= thresh { + break hi; + } + }; + self.low.wrapping_add(hi as usize) + } + } + + #[inline] + fn sample_single( + low_b: B1, + high_b: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low < high) { + return Err(Error::EmptyRange); + } + + if cfg!(target_pointer_width = "64") && high > (u32::MAX as usize) { + return UniformInt::::sample_single(low as u64, high as u64, rng) + .map(|x| x as usize); + } + + UniformInt::::sample_single(low as u32, high as u32, rng).map(|x| x as usize) + } + + #[inline] + fn sample_single_inclusive( + low_b: B1, + high_b: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = *low_b.borrow(); + let high = *high_b.borrow(); + if !(low <= high) { + return Err(Error::EmptyRange); + } + + if cfg!(target_pointer_width = "64") && high > (u32::MAX as usize) { + return UniformInt::::sample_single_inclusive(low as u64, high as u64, rng) + .map(|x| x as usize); + } + + UniformInt::::sample_single_inclusive(low as u32, high as u32, rng).map(|x| x as usize) + } +} + #[cfg(test)] mod tests { use super::*; - use crate::distr::Uniform; + use crate::distr::{Distribution, Uniform}; + use core::fmt::Debug; + use core::ops::Add; #[test] fn test_uniform_bad_limits_equal_int() { @@ -476,7 +653,7 @@ mod tests { );)* }}; } - t!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, i128, u128); + t!(i8, i16, i32, i64, i128, u8, u16, u32, u64, usize, u128); #[cfg(feature = "simd_support")] { @@ -518,4 +695,95 @@ mod tests { assert!(Uniform::try_from(100..=10).is_err()); assert!(Uniform::try_from(100..=99).is_err()); } + + #[test] + fn value_stability() { + fn test_samples>( + lb: T, + ub: T, + ub_excl: T, + expected: &[T], + ) where + Uniform: Distribution, + { + let mut rng = crate::test::rng(897); + let mut buf = [lb; 6]; + + for x in &mut buf[0..3] { + *x = T::Sampler::sample_single_inclusive(lb, ub, &mut rng).unwrap(); + } + + let distr = Uniform::new_inclusive(lb, ub).unwrap(); + for x in &mut buf[3..6] { + *x = rng.sample(&distr); + } + assert_eq!(&buf, expected); + + let mut rng = crate::test::rng(897); + + for x in &mut buf[0..3] { + *x = T::Sampler::sample_single(lb, ub_excl, &mut rng).unwrap(); + } + + let distr = Uniform::new(lb, ub_excl).unwrap(); + for x in &mut buf[3..6] { + *x = rng.sample(&distr); + } + assert_eq!(&buf, expected); + } + + test_samples(-105i8, 111, 112, &[-99, -48, 107, 72, -19, 56]); + test_samples(2i16, 1352, 1353, &[43, 361, 1325, 1109, 539, 1005]); + test_samples( + -313853i32, + 13513, + 13514, + &[-303803, -226673, 6912, -45605, -183505, -70668], + ); + test_samples( + 131521i64, + 6542165, + 6542166, + &[1838724, 5384489, 4893692, 3712948, 3951509, 4094926], + ); + test_samples( + -0x8000_0000_0000_0000_0000_0000_0000_0000i128, + -1, + 0, + &[ + -30725222750250982319765550926688025855, + -75088619368053423329503924805178012357, + -64950748766625548510467638647674468829, + -41794017901603587121582892414659436495, + -63623852319608406524605295913876414006, + -17404679390297612013597359206379189023, + ], + ); + test_samples(11u8, 218, 219, &[17, 66, 214, 181, 93, 165]); + test_samples(11u16, 218, 219, &[17, 66, 214, 181, 93, 165]); + test_samples(11u32, 218, 219, &[17, 66, 214, 181, 93, 165]); + test_samples(11u64, 218, 219, &[66, 181, 165, 127, 134, 139]); + test_samples(11u128, 218, 219, &[181, 127, 139, 167, 141, 197]); + test_samples(11usize, 218, 219, &[17, 66, 214, 181, 93, 165]); + + #[cfg(feature = "simd_support")] + { + let lb = Simd::from([11u8, 0, 128, 127]); + let ub = Simd::from([218, 254, 254, 254]); + let ub_excl = ub + Simd::splat(1); + test_samples( + lb, + ub, + ub_excl, + &[ + Simd::from([13, 5, 237, 130]), + Simd::from([126, 186, 149, 161]), + Simd::from([103, 86, 234, 252]), + Simd::from([35, 18, 225, 231]), + Simd::from([106, 153, 246, 177]), + Simd::from([195, 168, 149, 222]), + ], + ); + } + } } diff --git a/src/lib.rs b/src/lib.rs index ce0206db09b..3abbc5a2660 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,13 +179,13 @@ mod test { #[test] #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] fn test_random() { - let _n: usize = random(); + let _n: u64 = random(); let _f: f32 = random(); let _o: Option> = random(); #[allow(clippy::type_complexity)] let _many: ( (), - (usize, isize, Option<(u32, (bool,))>), + Option<(u32, (bool,))>, (u8, i8, u16, i16, u32, i32, u64, i64), (f32, (f64, (f64,))), ) = random(); diff --git a/src/rng.rs b/src/rng.rs index 9cea5fb20c6..9190747a295 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -102,7 +102,8 @@ pub trait Rng: RngCore { /// made from the given range. See also the [`Uniform`] distribution /// type which may be faster if sampling from the same range repeatedly. /// - /// Only `gen_range(low..high)` and `gen_range(low..=high)` are supported. + /// All types support `low..high_exclusive` and `low..=high` range syntax. + /// Unsigned integer types also support `..high_exclusive` and `..=high` syntax. /// /// # Panics /// @@ -116,13 +117,13 @@ pub trait Rng: RngCore { /// let mut rng = thread_rng(); /// /// // Exclusive range - /// let n: u32 = rng.gen_range(0..10); + /// let n: u32 = rng.gen_range(..10); /// println!("{}", n); /// let m: f64 = rng.gen_range(-40.0..1.3e5); /// println!("{}", m); /// /// // Inclusive range - /// let n: u32 = rng.gen_range(0..=10); + /// let n: u32 = rng.gen_range(..=10); /// println!("{}", n); /// ``` /// @@ -407,8 +408,8 @@ macro_rules! impl_fill { } } -impl_fill!(u16, u32, u64, usize, u128,); -impl_fill!(i8, i16, i32, i64, isize, i128,); +impl_fill!(u16, u32, u64, u128,); +impl_fill!(i8, i16, i32, i64, i128,); impl Fill for [T; N] where @@ -500,7 +501,7 @@ mod test { let a: u32 = r.gen_range(12..=24); assert!((12..=24).contains(&a)); - assert_eq!(r.gen_range(0u32..1), 0u32); + assert_eq!(r.gen_range(..1u32), 0u32); assert_eq!(r.gen_range(-12i64..-11), -12i64); assert_eq!(r.gen_range(3_000_000..3_000_001), 3_000_000); } diff --git a/src/seq/index.rs b/src/seq/index.rs index 53bd622e1c5..5bb1a7597f5 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -23,6 +23,9 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::collections::HashSet; +#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] +compile_error!("unsupported pointer width"); + /// A vector of indices. /// /// Multiple internal representations are possible. @@ -31,26 +34,29 @@ use std::collections::HashSet; pub enum IndexVec { #[doc(hidden)] U32(Vec), + #[cfg(target_pointer_width = "64")] #[doc(hidden)] - USize(Vec), + U64(Vec), } impl IndexVec { /// Returns the number of indices #[inline] pub fn len(&self) -> usize { - match *self { - IndexVec::U32(ref v) => v.len(), - IndexVec::USize(ref v) => v.len(), + match self { + IndexVec::U32(v) => v.len(), + #[cfg(target_pointer_width = "64")] + IndexVec::U64(v) => v.len(), } } /// Returns `true` if the length is 0. #[inline] pub fn is_empty(&self) -> bool { - match *self { - IndexVec::U32(ref v) => v.is_empty(), - IndexVec::USize(ref v) => v.is_empty(), + match self { + IndexVec::U32(v) => v.is_empty(), + #[cfg(target_pointer_width = "64")] + IndexVec::U64(v) => v.is_empty(), } } @@ -60,9 +66,10 @@ impl IndexVec { /// restrictions.) #[inline] pub fn index(&self, index: usize) -> usize { - match *self { - IndexVec::U32(ref v) => v[index] as usize, - IndexVec::USize(ref v) => v[index], + match self { + IndexVec::U32(v) => v[index] as usize, + #[cfg(target_pointer_width = "64")] + IndexVec::U64(v) => v[index] as usize, } } @@ -71,16 +78,18 @@ impl IndexVec { pub fn into_vec(self) -> Vec { match self { IndexVec::U32(v) => v.into_iter().map(|i| i as usize).collect(), - IndexVec::USize(v) => v, + #[cfg(target_pointer_width = "64")] + IndexVec::U64(v) => v.into_iter().map(|i| i as usize).collect(), } } /// Iterate over the indices as a sequence of `usize` values #[inline] pub fn iter(&self) -> IndexVecIter<'_> { - match *self { - IndexVec::U32(ref v) => IndexVecIter::U32(v.iter()), - IndexVec::USize(ref v) => IndexVecIter::USize(v.iter()), + match self { + IndexVec::U32(v) => IndexVecIter::U32(v.iter()), + #[cfg(target_pointer_width = "64")] + IndexVec::U64(v) => IndexVecIter::U64(v.iter()), } } } @@ -94,7 +103,8 @@ impl IntoIterator for IndexVec { fn into_iter(self) -> IndexVecIntoIter { match self { IndexVec::U32(v) => IndexVecIntoIter::U32(v.into_iter()), - IndexVec::USize(v) => IndexVecIntoIter::USize(v.into_iter()), + #[cfg(target_pointer_width = "64")] + IndexVec::U64(v) => IndexVecIntoIter::U64(v.into_iter()), } } } @@ -104,12 +114,15 @@ impl PartialEq for IndexVec { use self::IndexVec::*; match (self, other) { (U32(v1), U32(v2)) => v1 == v2, - (USize(v1), USize(v2)) => v1 == v2, - (U32(v1), USize(v2)) => { - (v1.len() == v2.len()) && (v1.iter().zip(v2.iter()).all(|(x, y)| *x as usize == *y)) + #[cfg(target_pointer_width = "64")] + (U64(v1), U64(v2)) => v1 == v2, + #[cfg(target_pointer_width = "64")] + (U32(v1), U64(v2)) => { + (v1.len() == v2.len()) && (v1.iter().zip(v2.iter()).all(|(x, y)| *x as u64 == *y)) } - (USize(v1), U32(v2)) => { - (v1.len() == v2.len()) && (v1.iter().zip(v2.iter()).all(|(x, y)| *x == *y as usize)) + #[cfg(target_pointer_width = "64")] + (U64(v1), U32(v2)) => { + (v1.len() == v2.len()) && (v1.iter().zip(v2.iter()).all(|(x, y)| *x == *y as u64)) } } } @@ -122,10 +135,11 @@ impl From> for IndexVec { } } -impl From> for IndexVec { +#[cfg(target_pointer_width = "64")] +impl From> for IndexVec { #[inline] - fn from(v: Vec) -> Self { - IndexVec::USize(v) + fn from(v: Vec) -> Self { + IndexVec::U64(v) } } @@ -134,8 +148,9 @@ impl From> for IndexVec { pub enum IndexVecIter<'a> { #[doc(hidden)] U32(slice::Iter<'a, u32>), + #[cfg(target_pointer_width = "64")] #[doc(hidden)] - USize(slice::Iter<'a, usize>), + U64(slice::Iter<'a, u64>), } impl<'a> Iterator for IndexVecIter<'a> { @@ -144,17 +159,19 @@ impl<'a> Iterator for IndexVecIter<'a> { #[inline] fn next(&mut self) -> Option { use self::IndexVecIter::*; - match *self { - U32(ref mut iter) => iter.next().map(|i| *i as usize), - USize(ref mut iter) => iter.next().cloned(), + match self { + U32(iter) => iter.next().map(|i| *i as usize), + #[cfg(target_pointer_width = "64")] + U64(iter) => iter.next().map(|i| *i as usize), } } #[inline] fn size_hint(&self) -> (usize, Option) { - match *self { - IndexVecIter::U32(ref v) => v.size_hint(), - IndexVecIter::USize(ref v) => v.size_hint(), + match self { + IndexVecIter::U32(v) => v.size_hint(), + #[cfg(target_pointer_width = "64")] + IndexVecIter::U64(v) => v.size_hint(), } } } @@ -166,8 +183,9 @@ impl<'a> ExactSizeIterator for IndexVecIter<'a> {} pub enum IndexVecIntoIter { #[doc(hidden)] U32(vec::IntoIter), + #[cfg(target_pointer_width = "64")] #[doc(hidden)] - USize(vec::IntoIter), + U64(vec::IntoIter), } impl Iterator for IndexVecIntoIter { @@ -176,18 +194,20 @@ impl Iterator for IndexVecIntoIter { #[inline] fn next(&mut self) -> Option { use self::IndexVecIntoIter::*; - match *self { - U32(ref mut v) => v.next().map(|i| i as usize), - USize(ref mut v) => v.next(), + match self { + U32(v) => v.next().map(|i| i as usize), + #[cfg(target_pointer_width = "64")] + U64(v) => v.next().map(|i| i as usize), } } #[inline] fn size_hint(&self) -> (usize, Option) { use self::IndexVecIntoIter::*; - match *self { - U32(ref v) => v.size_hint(), - USize(ref v) => v.size_hint(), + match self { + U32(v) => v.size_hint(), + #[cfg(target_pointer_width = "64")] + U64(v) => v.size_hint(), } } } @@ -225,9 +245,13 @@ where panic!("`amount` of samples must be less than or equal to `length`"); } if length > (u32::MAX as usize) { + #[cfg(target_pointer_width = "32")] + unreachable!(); + // We never want to use inplace here, but could use floyd's alg // Lazy version: always use the cache alg. - return sample_rejection(rng, length, amount); + #[cfg(target_pointer_width = "64")] + return sample_rejection(rng, length as u64, amount as u64); } let amount = amount as u32; let length = length as u32; @@ -285,7 +309,15 @@ where X: Into, { if length > (u32::MAX as usize) { - sample_efraimidis_spirakis(rng, length, weight, amount) + #[cfg(target_pointer_width = "32")] + unreachable!(); + + #[cfg(target_pointer_width = "64")] + { + let amount = amount as u64; + let length = length as u64; + sample_efraimidis_spirakis(rng, length, weight, amount) + } } else { assert!(amount <= u32::MAX as usize); let amount = amount as u32; @@ -402,7 +434,7 @@ where debug_assert!(amount <= length); let mut indices = Vec::with_capacity(amount as usize); for j in length - amount..length { - let t = rng.gen_range(0..=j); + let t = rng.gen_range(..=j); if let Some(pos) = indices.iter().position(|&x| x == t) { indices[pos] = j; } @@ -463,7 +495,8 @@ impl UInt for u32 { } } -impl UInt for usize { +#[cfg(target_pointer_width = "64")] +impl UInt for u64 { #[inline] fn zero() -> Self { 0 @@ -476,7 +509,7 @@ impl UInt for usize { #[inline] fn as_usize(self) -> usize { - self + self as usize } } @@ -521,20 +554,10 @@ mod test { #[test] #[cfg(feature = "serde")] fn test_serialization_index_vec() { - let some_index_vec = IndexVec::from(vec![254_usize, 234, 2, 1]); + let some_index_vec = IndexVec::from(vec![254_u32, 234, 2, 1]); let de_some_index_vec: IndexVec = bincode::deserialize(&bincode::serialize(&some_index_vec).unwrap()).unwrap(); - match (some_index_vec, de_some_index_vec) { - (IndexVec::U32(a), IndexVec::U32(b)) => { - assert_eq!(a, b); - } - (IndexVec::USize(a), IndexVec::USize(b)) => { - assert_eq!(a, b); - } - _ => { - panic!("failed to seralize/deserialize `IndexVec`") - } - } + assert_eq!(some_index_vec, de_some_index_vec); } #[test] @@ -610,7 +633,8 @@ mod test { assert!((i as usize) < len); } } - IndexVec::USize(_) => panic!("expected `IndexVec::U32`"), + #[cfg(target_pointer_width = "64")] + _ => panic!("expected `IndexVec::U32`"), } } diff --git a/src/seq/iterator.rs b/src/seq/iterator.rs index 46ecdfeac83..148093157da 100644 --- a/src/seq/iterator.rs +++ b/src/seq/iterator.rs @@ -9,7 +9,6 @@ //! `IteratorRandom` use super::coin_flipper::CoinFlipper; -use super::gen_index; #[allow(unused)] use super::IndexedRandom; use crate::Rng; @@ -71,7 +70,7 @@ pub trait IteratorRandom: Iterator + Sized { return match lower { 0 => None, 1 => self.next(), - _ => self.nth(gen_index(rng, lower)), + _ => self.nth(rng.gen_range(..lower)), }; } @@ -81,7 +80,7 @@ pub trait IteratorRandom: Iterator + Sized { // Continue until the iterator is exhausted loop { if lower > 1 { - let ix = gen_index(coin_flipper.rng, lower + consumed); + let ix = coin_flipper.rng.gen_range(..lower + consumed); let skip = if ix < lower { result = self.nth(ix); lower - (ix + 1) @@ -204,7 +203,7 @@ pub trait IteratorRandom: Iterator + Sized { // Continue, since the iterator was not exhausted for (i, elem) in self.enumerate() { - let k = gen_index(rng, i + 1 + amount); + let k = rng.gen_range(..i + 1 + amount); if let Some(slot) = buf.get_mut(k) { *slot = elem; } @@ -240,7 +239,7 @@ pub trait IteratorRandom: Iterator + Sized { // If the iterator stops once, then so do we. if reservoir.len() == amount { for (i, elem) in self.enumerate() { - let k = gen_index(rng, i + 1 + amount); + let k = rng.gen_range(..i + 1 + amount); if let Some(slot) = reservoir.get_mut(k) { *slot = elem; } diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 0015517907a..66459fe1746 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -43,23 +43,8 @@ pub use iterator::IteratorRandom; pub use slice::SliceChooseIter; pub use slice::{IndexedMutRandom, IndexedRandom, SliceRandom}; -use crate::Rng; - -// Sample a number uniformly between 0 and `ubound`. Uses 32-bit sampling where -// possible, primarily in order to produce the same output on 32-bit and 64-bit -// platforms. -#[inline] -fn gen_index(rng: &mut R, ubound: usize) -> usize { - if ubound <= (u32::MAX as usize) { - rng.gen_range(0..ubound as u32) as usize - } else { - rng.gen_range(0..ubound) - } -} - /// Low-level API for sampling indices pub mod index { - use super::gen_index; use crate::Rng; #[cfg(feature = "alloc")] @@ -84,7 +69,7 @@ pub mod index { // Floyd's algorithm let mut indices = [0; N]; for (i, j) in (len - N..len).enumerate() { - let t = gen_index(rng, j + 1); + let t = rng.gen_range(..j + 1); if let Some(pos) = indices[0..i].iter().position(|&x| x == t) { indices[pos] = j; } diff --git a/src/seq/slice.rs b/src/seq/slice.rs index 75f304c9220..930e5450943 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -9,7 +9,7 @@ //! `IndexedRandom`, `IndexedMutRandom`, `SliceRandom` use super::increasing_uniform::IncreasingUniform; -use super::{gen_index, index}; +use super::index; #[cfg(feature = "alloc")] use crate::distr::uniform::{SampleBorrow, SampleUniform}; #[cfg(feature = "alloc")] @@ -57,7 +57,7 @@ pub trait IndexedRandom: Index { if self.is_empty() { None } else { - Some(&self[gen_index(rng, self.len())]) + Some(&self[rng.gen_range(..self.len())]) } } @@ -259,7 +259,7 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { None } else { let len = self.len(); - Some(&mut self[gen_index(rng, len)]) + Some(&mut self[rng.gen_range(..len)]) } } @@ -399,7 +399,7 @@ impl SliceRandom for [T] { // It ensures that the last `amount` elements of the slice // are randomly selected from the whole slice. - // `IncreasingUniform::next_index()` is faster than `gen_index` + // `IncreasingUniform::next_index()` is faster than `Rng::gen_range` // but only works for 32 bit integers // So we must use the slow method if the slice is longer than that. if self.len() < (u32::MAX as usize) { @@ -410,7 +410,7 @@ impl SliceRandom for [T] { } } else { for i in m..self.len() { - let index = gen_index(rng, i + 1); + let index = rng.gen_range(..i + 1); self.swap(i, index); } } From f2638201ffe521e855e149bbecf48ecb9e130103 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 23 Sep 2024 18:02:25 +0100 Subject: [PATCH 406/443] Poisson: split Knuth/Rejection methods (#1493) --- benches/benches/distr.rs | 185 ++++++++++++++----------------------- rand_distr/src/lib.rs | 2 +- rand_distr/src/poisson.rs | 187 ++++++++++++++++++++++++-------------- 3 files changed, 191 insertions(+), 183 deletions(-) diff --git a/benches/benches/distr.rs b/benches/benches/distr.rs index 3097f3a9294..63d6a1034ee 100644 --- a/benches/benches/distr.rs +++ b/benches/benches/distr.rs @@ -11,95 +11,46 @@ // Rustfmt splits macro invocations to shorten lines; in this case longer-lines are more readable #![rustfmt::skip] -const RAND_BENCH_N: u64 = 1000; - use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use criterion_cycles_per_byte::CyclesPerByte; -use core::mem::size_of; - use rand::prelude::*; use rand_distr::*; // At this time, distributions are optimised for 64-bit platforms. use rand_pcg::Pcg64Mcg; +const ITER_ELTS: u64 = 100; + macro_rules! distr_int { ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { - $group.throughput(Throughput::Bytes( - size_of::<$ty>() as u64 * RAND_BENCH_N)); $group.bench_function($fnn, |c| { let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; - c.iter(|| { - let mut accum: $ty = 0; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum = accum.wrapping_add(x); - } - accum - }); + c.iter(|| distr.sample(&mut rng)); }); }; } macro_rules! distr_float { ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { - $group.throughput(Throughput::Bytes( - size_of::<$ty>() as u64 * RAND_BENCH_N)); $group.bench_function($fnn, |c| { let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; - c.iter(|| { - let mut accum = 0.; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum += x; - } - accum - }); - }); - }; -} - -macro_rules! distr { - ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { - $group.throughput(Throughput::Bytes( - size_of::<$ty>() as u64 * RAND_BENCH_N)); - $group.bench_function($fnn, |c| { - let mut rng = Pcg64Mcg::from_os_rng(); - let distr = $distr; - - c.iter(|| { - let mut accum: u32 = 0; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum = accum.wrapping_add(x as u32); - } - accum - }); + c.iter(|| Distribution::<$ty>::sample(&distr, &mut rng)); }); }; } macro_rules! distr_arr { ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { - $group.throughput(Throughput::Bytes( - size_of::<$ty>() as u64 * RAND_BENCH_N)); $group.bench_function($fnn, |c| { let mut rng = Pcg64Mcg::from_os_rng(); let distr = $distr; - c.iter(|| { - let mut accum: u32 = 0; - for _ in 0..RAND_BENCH_N { - let x: $ty = distr.sample(&mut rng); - accum = accum.wrapping_add(x[0] as u32); - } - accum - }); + c.iter(|| Distribution::<$ty>::sample(&distr, &mut rng)); }); }; } @@ -111,122 +62,126 @@ macro_rules! sample_binomial { } fn bench(c: &mut Criterion) { - { let mut g = c.benchmark_group("exp"); distr_float!(g, "exp", f64, Exp::new(1.23 * 4.56).unwrap()); distr_float!(g, "exp1_specialized", f64, Exp1); distr_float!(g, "exp1_general", f64, Exp::new(1.).unwrap()); - } + g.finish(); - { let mut g = c.benchmark_group("normal"); distr_float!(g, "normal", f64, Normal::new(-1.23, 4.56).unwrap()); distr_float!(g, "standardnormal_specialized", f64, StandardNormal); distr_float!(g, "standardnormal_general", f64, Normal::new(0., 1.).unwrap()); distr_float!(g, "log_normal", f64, LogNormal::new(-1.23, 4.56).unwrap()); - g.throughput(Throughput::Bytes(size_of::() as u64 * RAND_BENCH_N)); + g.throughput(Throughput::Elements(ITER_ELTS)); g.bench_function("iter", |c| { use core::f64::consts::{E, PI}; let mut rng = Pcg64Mcg::from_os_rng(); let distr = Normal::new(-E, PI).unwrap(); - let mut iter = distr.sample_iter(&mut rng); c.iter(|| { - let mut accum = 0.0; - for _ in 0..RAND_BENCH_N { - accum += iter.next().unwrap(); - } - accum + distr.sample_iter(&mut rng) + .take(ITER_ELTS as usize) + .fold(0.0, |a, r| a + r) }); }); - } + g.finish(); - { let mut g = c.benchmark_group("skew_normal"); distr_float!(g, "shape_zero", f64, SkewNormal::new(0.0, 1.0, 0.0).unwrap()); distr_float!(g, "shape_positive", f64, SkewNormal::new(0.0, 1.0, 100.0).unwrap()); distr_float!(g, "shape_negative", f64, SkewNormal::new(0.0, 1.0, -100.0).unwrap()); - } + g.finish(); - { let mut g = c.benchmark_group("gamma"); - distr_float!(g, "gamma_large_shape", f64, Gamma::new(10., 1.0).unwrap()); - distr_float!(g, "gamma_small_shape", f64, Gamma::new(0.1, 1.0).unwrap()); - distr_float!(g, "beta_small_param", f64, Beta::new(0.1, 0.1).unwrap()); - distr_float!(g, "beta_large_param_similar", f64, Beta::new(101., 95.).unwrap()); - distr_float!(g, "beta_large_param_different", f64, Beta::new(10., 1000.).unwrap()); - distr_float!(g, "beta_mixed_param", f64, Beta::new(0.5, 100.).unwrap()); - } + distr_float!(g, "large_shape", f64, Gamma::new(10., 1.0).unwrap()); + distr_float!(g, "small_shape", f64, Gamma::new(0.1, 1.0).unwrap()); + g.finish(); + + let mut g = c.benchmark_group("beta"); + distr_float!(g, "small_param", f64, Beta::new(0.1, 0.1).unwrap()); + distr_float!(g, "large_param_similar", f64, Beta::new(101., 95.).unwrap()); + distr_float!(g, "large_param_different", f64, Beta::new(10., 1000.).unwrap()); + distr_float!(g, "mixed_param", f64, Beta::new(0.5, 100.).unwrap()); + g.finish(); - { let mut g = c.benchmark_group("cauchy"); distr_float!(g, "cauchy", f64, Cauchy::new(4.2, 6.9).unwrap()); - } + g.finish(); - { let mut g = c.benchmark_group("triangular"); distr_float!(g, "triangular", f64, Triangular::new(0., 1., 0.9).unwrap()); - } + g.finish(); - { let mut g = c.benchmark_group("geometric"); distr_int!(g, "geometric", u64, Geometric::new(0.5).unwrap()); distr_int!(g, "standard_geometric", u64, StandardGeometric); - } + g.finish(); - { let mut g = c.benchmark_group("weighted"); - distr_int!(g, "weighted_i8", usize, WeightedIndex::new([1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); - distr_int!(g, "weighted_u32", usize, WeightedIndex::new([1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); - distr_int!(g, "weighted_f64", usize, WeightedIndex::new([1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); - distr_int!(g, "weighted_large_set", usize, WeightedIndex::new((0..10000).rev().chain(1..10001)).unwrap()); - distr_int!(g, "weighted_alias_method_i8", usize, WeightedAliasIndex::new(vec![1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); - distr_int!(g, "weighted_alias_method_u32", usize, WeightedAliasIndex::new(vec![1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); - distr_int!(g, "weighted_alias_method_f64", usize, WeightedAliasIndex::new(vec![1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); - distr_int!(g, "weighted_alias_method_large_set", usize, WeightedAliasIndex::new((0..10000).rev().chain(1..10001).collect()).unwrap()); - } + distr_int!(g, "i8", usize, WeightedIndex::new([1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "u32", usize, WeightedIndex::new([1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "f64", usize, WeightedIndex::new([1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); + distr_int!(g, "large_set", usize, WeightedIndex::new((0..10000).rev().chain(1..10001)).unwrap()); + distr_int!(g, "alias_method_i8", usize, WeightedAliasIndex::new(vec![1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "alias_method_u32", usize, WeightedAliasIndex::new(vec![1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); + distr_int!(g, "alias_method_f64", usize, WeightedAliasIndex::new(vec![1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); + distr_int!(g, "alias_method_large_set", usize, WeightedAliasIndex::new((0..10000).rev().chain(1..10001).collect()).unwrap()); + g.finish(); - { let mut g = c.benchmark_group("binomial"); - sample_binomial!(g, "binomial", 20, 0.7); - sample_binomial!(g, "binomial_small", 1_000_000, 1e-30); - sample_binomial!(g, "binomial_1", 1, 0.9); - sample_binomial!(g, "binomial_10", 10, 0.9); - sample_binomial!(g, "binomial_100", 100, 0.99); - sample_binomial!(g, "binomial_1000", 1000, 0.01); - sample_binomial!(g, "binomial_1e12", 1_000_000_000_000, 0.2); - } + sample_binomial!(g, "small", 1_000_000, 1e-30); + sample_binomial!(g, "1", 1, 0.9); + sample_binomial!(g, "10", 10, 0.9); + sample_binomial!(g, "100", 100, 0.99); + sample_binomial!(g, "1000", 1000, 0.01); + sample_binomial!(g, "1e12", 1_000_000_000_000, 0.2); + g.finish(); - { let mut g = c.benchmark_group("poisson"); - distr_float!(g, "poisson", f64, Poisson::new(4.0).unwrap()); + for lambda in [1f64, 4.0, 10.0, 100.0].into_iter() { + let name = format!("{lambda}"); + distr_float!(g, name, f64, Poisson::new(lambda).unwrap()); } + g.throughput(Throughput::Elements(ITER_ELTS)); + g.bench_function("variable", |c| { + let mut rng = Pcg64Mcg::from_os_rng(); + let ldistr = Uniform::new(0.1, 10.0).unwrap(); + + c.iter(|| { + let l = rng.sample(ldistr); + let distr = Poisson::new(l * l).unwrap(); + Distribution::::sample_iter(&distr, &mut rng) + .take(ITER_ELTS as usize) + .fold(0.0, |a, r| a + r) + }) + }); + g.finish(); - { let mut g = c.benchmark_group("zipf"); distr_float!(g, "zipf", f64, Zipf::new(10, 1.5).unwrap()); distr_float!(g, "zeta", f64, Zeta::new(1.5).unwrap()); - } + g.finish(); - { let mut g = c.benchmark_group("bernoulli"); - distr!(g, "bernoulli", bool, Bernoulli::new(0.18).unwrap()); - } + g.bench_function("bernoulli", |c| { + let mut rng = Pcg64Mcg::from_os_rng(); + let distr = Bernoulli::new(0.18).unwrap(); + c.iter(|| distr.sample(&mut rng)) + }); + g.finish(); - { - let mut g = c.benchmark_group("circle"); + let mut g = c.benchmark_group("unit"); distr_arr!(g, "circle", [f64; 2], UnitCircle); - } - - { - let mut g = c.benchmark_group("sphere"); distr_arr!(g, "sphere", [f64; 3], UnitSphere); - } + g.finish(); } criterion_group!( name = benches; - config = Criterion::default().with_measurement(CyclesPerByte); + config = Criterion::default().with_measurement(CyclesPerByte) + .warm_up_time(core::time::Duration::from_secs(1)) + .measurement_time(core::time::Duration::from_secs(2)); targets = bench ); criterion_main!(benches); diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 90a534ff8cb..03fad85c919 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -211,7 +211,7 @@ mod normal; mod normal_inverse_gaussian; mod pareto; mod pert; -mod poisson; +pub(crate) mod poisson; mod skew_normal; mod student_t; mod triangular; diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 09eae374dbd..26e7712b2c2 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -45,18 +45,10 @@ use rand::Rng; /// ``` #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Poisson +pub struct Poisson(Method) where F: Float + FloatConst, - Standard: Distribution, -{ - lambda: F, - // precalculated values - exp_lambda: F, - log_lambda: F, - sqrt_2lambda: F, - magic_val: F, -} + Standard: Distribution; /// Error type returned from [`Poisson::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -81,6 +73,50 @@ impl fmt::Display for Error { #[cfg(feature = "std")] impl std::error::Error for Error {} +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub(crate) struct KnuthMethod { + exp_lambda: F, +} + +impl KnuthMethod { + pub(crate) fn new(lambda: F) -> Self { + KnuthMethod { + exp_lambda: (-lambda).exp(), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct RejectionMethod { + lambda: F, + log_lambda: F, + sqrt_2lambda: F, + magic_val: F, +} + +impl RejectionMethod { + pub(crate) fn new(lambda: F) -> Self { + let log_lambda = lambda.ln(); + let sqrt_2lambda = (F::from(2.0).unwrap() * lambda).sqrt(); + let magic_val = lambda * log_lambda - crate::utils::log_gamma(F::one() + lambda); + RejectionMethod { + lambda, + log_lambda, + sqrt_2lambda, + magic_val, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +enum Method { + Knuth(KnuthMethod), + Rejection(RejectionMethod), +} + impl Poisson where F: Float + FloatConst, @@ -104,77 +140,94 @@ where if !(lambda > F::zero()) { return Err(Error::ShapeTooSmall); } - let log_lambda = lambda.ln(); - Ok(Poisson { - lambda, - exp_lambda: (-lambda).exp(), - log_lambda, - sqrt_2lambda: (F::from(2.0).unwrap() * lambda).sqrt(), - magic_val: lambda * log_lambda - crate::utils::log_gamma(F::one() + lambda), - }) + + // Use the Knuth method only for low expected values + let method = if lambda < F::from(12.0).unwrap() { + Method::Knuth(KnuthMethod::new(lambda)) + } else { + Method::Rejection(RejectionMethod::new(lambda)) + }; + + Ok(Poisson(method)) } } -impl Distribution for Poisson +impl Distribution for KnuthMethod where F: Float + FloatConst, Standard: Distribution, { - #[inline] fn sample(&self, rng: &mut R) -> F { - // using the algorithm from Numerical Recipes in C - - // for low expected values use the Knuth method - if self.lambda < F::from(12.0).unwrap() { - let mut result = F::one(); - let mut p = rng.random::(); - while p > self.exp_lambda { - p = p * rng.random::(); - result = result + F::one(); - } - result - F::one() + let mut result = F::one(); + let mut p = rng.random::(); + while p > self.exp_lambda { + p = p * rng.random::(); + result = result + F::one(); } - // high expected values - rejection method - else { - // we use the Cauchy distribution as the comparison distribution - // f(x) ~ 1/(1+x^2) - let cauchy = Cauchy::new(F::zero(), F::one()).unwrap(); - let mut result; + result - F::one() + } +} + +impl Distribution for RejectionMethod +where + F: Float + FloatConst, + Standard: Distribution, +{ + fn sample(&self, rng: &mut R) -> F { + // The algorithm from Numerical Recipes in C + + // we use the Cauchy distribution as the comparison distribution + // f(x) ~ 1/(1+x^2) + let cauchy = Cauchy::new(F::zero(), F::one()).unwrap(); + let mut result; + + loop { + let mut comp_dev; loop { - let mut comp_dev; - - loop { - // draw from the Cauchy distribution - comp_dev = rng.sample(cauchy); - // shift the peak of the comparison distribution - result = self.sqrt_2lambda * comp_dev + self.lambda; - // repeat the drawing until we are in the range of possible values - if result >= F::zero() { - break; - } - } - // now the result is a random variable greater than 0 with Cauchy distribution - // the result should be an integer value - result = result.floor(); - - // this is the ratio of the Poisson distribution to the comparison distribution - // the magic value scales the distribution function to a range of approximately 0-1 - // since it is not exact, we multiply the ratio by 0.9 to avoid ratios greater than 1 - // this doesn't change the resulting distribution, only increases the rate of failed drawings - let check = F::from(0.9).unwrap() - * (F::one() + comp_dev * comp_dev) - * (result * self.log_lambda - - crate::utils::log_gamma(F::one() + result) - - self.magic_val) - .exp(); - - // check with uniform random value - if below the threshold, we are within the target distribution - if rng.random::() <= check { + // draw from the Cauchy distribution + comp_dev = rng.sample(cauchy); + // shift the peak of the comparison distribution + result = self.sqrt_2lambda * comp_dev + self.lambda; + // repeat the drawing until we are in the range of possible values + if result >= F::zero() { break; } } - result + // now the result is a random variable greater than 0 with Cauchy distribution + // the result should be an integer value + result = result.floor(); + + // this is the ratio of the Poisson distribution to the comparison distribution + // the magic value scales the distribution function to a range of approximately 0-1 + // since it is not exact, we multiply the ratio by 0.9 to avoid ratios greater than 1 + // this doesn't change the resulting distribution, only increases the rate of failed drawings + let check = F::from(0.9).unwrap() + * (F::one() + comp_dev * comp_dev) + * (result * self.log_lambda + - crate::utils::log_gamma(F::one() + result) + - self.magic_val) + .exp(); + + // check with uniform random value - if below the threshold, we are within the target distribution + if rng.random::() <= check { + break; + } + } + result + } +} + +impl Distribution for Poisson +where + F: Float + FloatConst, + Standard: Distribution, +{ + #[inline] + fn sample(&self, rng: &mut R) -> F { + match &self.0 { + Method::Knuth(method) => method.sample(rng), + Method::Rejection(method) => method.sample(rng), } } } From e2092e925148b81740f91963e7aac8846987bda2 Mon Sep 17 00:00:00 2001 From: Benjamin Lieser Date: Tue, 1 Oct 2024 15:27:39 +0200 Subject: [PATCH 407/443] Poisson u64 sampling (#1498) This addresses https://github.com/rust-random/rand/issues/1497 by adding `Distribution` It also solves https://github.com/rust-random/rand/issues/1312 by not allowing `lambda` bigger than `1.844e19` (this also makes them always fit into `u64`) --- CHANGELOG.md | 2 ++ rand_distr/src/poisson.rs | 44 ++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d071e093916..15347e017d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) - Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) +- Implement `Distribution` for `Poisson` (#1498) +- Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 26e7712b2c2..759f39cde79 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -23,10 +23,6 @@ use rand::Rng; /// This distribution has density function: /// `f(k) = λ^k * exp(-λ) / k!` for `k >= 0`. /// -/// # Known issues -/// -/// See documentation of [`Poisson::new`]. -/// /// # Plot /// /// The following plot shows the Poisson distribution with various values of `λ`. @@ -40,7 +36,7 @@ use rand::Rng; /// use rand_distr::{Poisson, Distribution}; /// /// let poi = Poisson::new(2.0).unwrap(); -/// let v = poi.sample(&mut rand::thread_rng()); +/// let v: f64 = poi.sample(&mut rand::thread_rng()); /// println!("{} is from a Poisson(2) distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] @@ -52,13 +48,13 @@ where /// Error type returned from [`Poisson::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -// Marked non_exhaustive to allow a new error code in the solution to #1312. -#[non_exhaustive] pub enum Error { /// `lambda <= 0` ShapeTooSmall, /// `lambda = ∞` or `lambda = nan` NonFinite, + /// `lambda` is too large, see [Poisson::MAX_LAMBDA] + ShapeTooLarge, } impl fmt::Display for Error { @@ -66,6 +62,9 @@ impl fmt::Display for Error { f.write_str(match self { Error::ShapeTooSmall => "lambda is not positive in Poisson distribution", Error::NonFinite => "lambda is infinite or nan in Poisson distribution", + Error::ShapeTooLarge => { + "lambda is too large in Poisson distribution, see Poisson::MAX_LAMBDA" + } }) } } @@ -125,14 +124,7 @@ where /// Construct a new `Poisson` with the given shape parameter /// `lambda`. /// - /// # Known issues - /// - /// Although this method should return an [`Error`] on invalid parameters, - /// some (extreme) values of `lambda` are known to return a [`Poisson`] - /// object which hangs when [sampled](Distribution::sample). - /// Large (less extreme) values of `lambda` may result in successful - /// sampling but with reduced precision. - /// See [#1312](https://github.com/rust-random/rand/issues/1312). + /// The maximum allowed lambda is [MAX_LAMBDA](Self::MAX_LAMBDA). pub fn new(lambda: F) -> Result, Error> { if !lambda.is_finite() { return Err(Error::NonFinite); @@ -145,11 +137,25 @@ where let method = if lambda < F::from(12.0).unwrap() { Method::Knuth(KnuthMethod::new(lambda)) } else { + if lambda > F::from(Self::MAX_LAMBDA).unwrap() { + return Err(Error::ShapeTooLarge); + } Method::Rejection(RejectionMethod::new(lambda)) }; Ok(Poisson(method)) } + + /// The maximum supported value of `lambda` + /// + /// This value was selected such that + /// `MAX_LAMBDA + 1e6 * sqrt(MAX_LAMBDA) < 2^64 - 1`, + /// thus ensuring that the probability of sampling a value larger than + /// `u64::MAX` is less than 1e-1000. + /// + /// Applying this limit also solves + /// [#1312](https://github.com/rust-random/rand/issues/1312). + pub const MAX_LAMBDA: f64 = 1.844e19; } impl Distribution for KnuthMethod @@ -232,6 +238,14 @@ where } } +impl Distribution for Poisson { + #[inline] + fn sample(&self, rng: &mut R) -> u64 { + // `as` from float to int saturates + as Distribution>::sample(self, rng) as u64 + } +} + #[cfg(test)] mod test { use super::*; From 66b11eb17bb256bc7461278d38b2671685db532a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 1 Oct 2024 14:59:11 +0100 Subject: [PATCH 408/443] =?UTF-8?q?Rename=20gen=5Fiter=20=E2=86=92=20rando?= =?UTF-8?q?m=5Fiter,=20misc..=20(#1500)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This extracts the non-inherent-methods stuff from #1492. --- CHANGELOG.md | 1 + README.md | 31 +++++---- benches/benches/seq_choose.rs | 2 +- rand_core/src/lib.rs | 6 +- rand_distr/src/weighted_alias.rs | 7 +- src/lib.rs | 23 ++++--- src/rng.rs | 108 ++++++++++++++++--------------- src/seq/index.rs | 10 +-- 8 files changed, 99 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15347e017d9..3300b9ad9fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) - Implement `Distribution` for `Poisson` (#1498) - Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) +- Rename `Rng::gen_iter` to `random_iter` (#1500) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/README.md b/README.md index 18f22a89eb8..25341ac2d03 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,31 @@ [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -A Rust library for random number generation, featuring: +Rand is a Rust library supporting random generators: -- Easy random value generation and usage via the [`Rng`](https://docs.rs/rand/*/rand/trait.Rng.html), - [`SliceRandom`](https://docs.rs/rand/*/rand/seq/trait.SliceRandom.html) and - [`IteratorRandom`](https://docs.rs/rand/*/rand/seq/trait.IteratorRandom.html) traits -- Secure seeding via the [`getrandom` crate](https://crates.io/crates/getrandom) - and fast, convenient generation via [`thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html) -- A modular design built over [`rand_core`](https://crates.io/crates/rand_core) - ([see the book](https://rust-random.github.io/book/crates.html)) +- A standard RNG trait: [`rand_core::RngCore`](https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html) - Fast implementations of the best-in-class [cryptographic](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and - [non-cryptographic](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators + [non-cryptographic](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) +- [`rand::thread_rng`](https://docs.rs/rand/latest/rand/fn.thread_rng.html) is an asymtotically-fast, reasonably secure generator available on all `std` targets +- Secure seeding via the [`getrandom` crate](https://crates.io/crates/getrandom) + +Supporting random value generation and random processes: + +- [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html) random value generation +- Ranged [`Uniform`](https://docs.rs/rand/latest/rand/distributions/struct.Uniform.html) number generation for many types - A flexible [`distributions`](https://docs.rs/rand/*/rand/distr/index.html) module - Samplers for a large number of random number distributions via our own [`rand_distr`](https://docs.rs/rand_distr) and via the [`statrs`](https://docs.rs/statrs/0.13.0/statrs/) +- Random processes (mostly choose and shuffle) via [`rand::seq`](https://docs.rs/rand/latest/rand/seq/index.html) traits + +All with: + - [Portably reproducible output](https://rust-random.github.io/book/portability.html) - `#[no_std]` compatibility (partial) - *Many* performance optimisations -It's also worth pointing out what `rand` *is not*: +It's also worth pointing out what Rand *is not*: - Small. Most low-level crates are small, but the higher-level `rand` and `rand_distr` each contain a lot of functionality. @@ -73,8 +78,7 @@ Rand is built with these features enabled by default: - `alloc` (implied by `std`) enables functionality requiring an allocator - `getrandom` (implied by `std`) is an optional dependency providing the code behind `rngs::OsRng` -- `std_rng` enables inclusion of `StdRng`, `thread_rng` and `random` - (the latter two *also* require that `std` be enabled) +- `std_rng` enables inclusion of `StdRng`, `thread_rng` Optionally, the following dependencies can be enabled: @@ -94,8 +98,7 @@ experimental `simd_support` feature. Rand supports limited functionality in `no_std` mode (enabled via `default-features = false`). In this case, `OsRng` and `from_os_rng` are unavailable (unless `getrandom` is enabled), large parts of `seq` are -unavailable (unless `alloc` is enabled), and `thread_rng` and `random` are -unavailable. +unavailable (unless `alloc` is enabled), and `thread_rng` is unavailable. ## Portability and platform support diff --git a/benches/benches/seq_choose.rs b/benches/benches/seq_choose.rs index f418f9cc4dc..58c4f894eaf 100644 --- a/benches/benches/seq_choose.rs +++ b/benches/benches/seq_choose.rs @@ -19,7 +19,7 @@ criterion_group!( criterion_main!(benches); pub fn bench(c: &mut Criterion) { - c.bench_function("seq_slice_choose_1_of_1000", |b| { + c.bench_function("seq_slice_choose_1_of_100", |b| { let mut rng = Pcg32::from_rng(thread_rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 3c16a9767c9..39e95d95db2 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -54,11 +54,11 @@ pub use getrandom; #[cfg(feature = "getrandom")] pub use os::OsRng; -/// The core of a random number generator. +/// Implementation-level interface for RNGs /// /// This trait encapsulates the low-level functionality common to all /// generators, and is the "back end", to be implemented by generators. -/// End users should normally use the `Rng` trait from the [`rand`] crate, +/// End users should normally use the [`rand::Rng`] trait /// which is automatically implemented for every type implementing `RngCore`. /// /// Three different methods for generating random data are provided since the @@ -129,7 +129,7 @@ pub use os::OsRng; /// rand_core::impl_try_rng_from_rng_core!(CountingRng); /// ``` /// -/// [`rand`]: https://docs.rs/rand +/// [`rand::Rng`]: https://docs.rs/rand/latest/rand/trait.Rng.html /// [`fill_bytes`]: RngCore::fill_bytes /// [`next_u32`]: RngCore::next_u32 /// [`next_u64`]: RngCore::next_u64 diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 593219cafdd..537060f3888 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -275,9 +275,10 @@ where } } -/// Trait that must be implemented for weights, that are used with -/// [`WeightedAliasIndex`]. Currently no guarantees on the correctness of -/// [`WeightedAliasIndex`] are given for custom implementations of this trait. +/// Weight bound for [`WeightedAliasIndex`] +/// +/// Currently no guarantees on the correctness of [`WeightedAliasIndex`] are +/// given for custom implementations of this trait. pub trait AliasableWeight: Sized + Copy diff --git a/src/lib.rs b/src/lib.rs index 3abbc5a2660..958c15d481c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,25 +14,24 @@ //! //! # Quick Start //! -//! To get you started quickly, the easiest and highest-level way to get -//! a random value is to use [`random()`]; alternatively you can use -//! [`thread_rng()`]. The [`Rng`] trait provides a useful API on all RNGs, while -//! the [`distr`] and [`seq`] modules provide further -//! functionality on top of RNGs. -//! //! ``` +//! // The prelude import enables methods we use below, specifically +//! // Rng::random, Rng::sample, SliceRandom::shuffle and IndexedRandom::choose. //! use rand::prelude::*; //! -//! if rand::random() { // generates a boolean -//! // Try printing a random unicode code point (probably a bad idea)! -//! println!("char: {}", rand::random::()); -//! } -//! +//! // Get an RNG: //! let mut rng = rand::thread_rng(); -//! let y: f64 = rng.random(); // generates a float between 0 and 1 //! +//! // Try printing a random unicode code point (probably a bad idea)! +//! println!("char: '{}'", rng.random::()); +//! // Try printing a random alphanumeric value instead! +//! println!("alpha: '{}'", rng.sample(rand::distr::Alphanumeric) as char); +//! +//! // Generate and shuffle a sequence: //! let mut nums: Vec = (1..100).collect(); //! nums.shuffle(&mut rng); +//! // And take a random pick (yes, we didn't need to shuffle first!): +//! let _ = nums.choose(&mut rng); //! ``` //! //! # The Book diff --git a/src/rng.rs b/src/rng.rs index 9190747a295..7c9e887a2d7 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -15,10 +15,14 @@ use core::num::Wrapping; use core::{mem, slice}; use rand_core::RngCore; -/// An automatically-implemented extension trait on [`RngCore`] providing high-level -/// generic methods for sampling values and other convenience methods. +/// User-level interface for RNGs /// -/// This is the primary trait to use when generating random values. +/// [`RngCore`] is the `dyn`-safe implementation-level interface for Random +/// (Number) Generators. This trait, `Rng`, provides a user-level interface on +/// RNGs. It is implemented automatically for any `R: RngCore`. +/// +/// This trait must usually be brought into scope via `use rand::Rng;` or +/// `use rand::prelude::*;`. /// /// # Generic usage /// @@ -96,55 +100,13 @@ pub trait Rng: RngCore { Standard.sample(self) } - /// Generate a random value in the given range. - /// - /// This function is optimised for the case that only a single sample is - /// made from the given range. See also the [`Uniform`] distribution - /// type which may be faster if sampling from the same range repeatedly. - /// - /// All types support `low..high_exclusive` and `low..=high` range syntax. - /// Unsigned integer types also support `..high_exclusive` and `..=high` syntax. - /// - /// # Panics - /// - /// Panics if the range is empty, or if `high - low` overflows for floats. - /// - /// # Example - /// - /// ``` - /// use rand::{thread_rng, Rng}; - /// - /// let mut rng = thread_rng(); - /// - /// // Exclusive range - /// let n: u32 = rng.gen_range(..10); - /// println!("{}", n); - /// let m: f64 = rng.gen_range(-40.0..1.3e5); - /// println!("{}", m); - /// - /// // Inclusive range - /// let n: u32 = rng.gen_range(..=10); - /// println!("{}", n); - /// ``` - /// - /// [`Uniform`]: distr::uniform::Uniform - #[track_caller] - fn gen_range(&mut self, range: R) -> T - where - T: SampleUniform, - R: SampleRange, - { - assert!(!range.is_empty(), "cannot sample empty range"); - range.sample_single(self).unwrap() - } - - /// Generate values via an iterator + /// Return an iterator over [`random`](Self::random) variates /// /// This is a just a wrapper over [`Rng::sample_iter`] using /// [`distr::Standard`]. /// /// Note: this method consumes its argument. Use - /// `(&mut rng).gen_iter()` to avoid consuming the RNG. + /// `(&mut rng).random_iter()` to avoid consuming the RNG. /// /// # Example /// @@ -152,11 +114,11 @@ pub trait Rng: RngCore { /// use rand::{rngs::mock::StepRng, Rng}; /// /// let rng = StepRng::new(1, 1); - /// let v: Vec = rng.gen_iter().take(5).collect(); + /// let v: Vec = rng.random_iter().take(5).collect(); /// assert_eq!(&v, &[1, 2, 3, 4, 5]); /// ``` #[inline] - fn gen_iter(self) -> distr::DistIter + fn random_iter(self) -> distr::DistIter where Self: Sized, Standard: Distribution, @@ -247,6 +209,48 @@ pub trait Rng: RngCore { dest.fill(self) } + /// Generate a random value in the given range. + /// + /// This function is optimised for the case that only a single sample is + /// made from the given range. See also the [`Uniform`] distribution + /// type which may be faster if sampling from the same range repeatedly. + /// + /// All types support `low..high_exclusive` and `low..=high` range syntax. + /// Unsigned integer types also support `..high_exclusive` and `..=high` syntax. + /// + /// # Panics + /// + /// Panics if the range is empty, or if `high - low` overflows for floats. + /// + /// # Example + /// + /// ``` + /// use rand::{thread_rng, Rng}; + /// + /// let mut rng = thread_rng(); + /// + /// // Exclusive range + /// let n: u32 = rng.gen_range(..10); + /// println!("{}", n); + /// let m: f64 = rng.gen_range(-40.0..1.3e5); + /// println!("{}", m); + /// + /// // Inclusive range + /// let n: u32 = rng.gen_range(..=10); + /// println!("{}", n); + /// ``` + /// + /// [`Uniform`]: distr::uniform::Uniform + #[track_caller] + fn gen_range(&mut self, range: R) -> T + where + T: SampleUniform, + R: SampleRange, + { + assert!(!range.is_empty(), "cannot sample empty range"); + range.sample_single(self).unwrap() + } + /// Return a bool with a probability `p` of being true. /// /// See also the [`Bernoulli`] distribution, which may be faster if @@ -316,7 +320,7 @@ pub trait Rng: RngCore { since = "0.9.0", note = "Renamed to `random` to avoid conflict with the new `gen` keyword in Rust 2024." )] - fn gen(&mut self) -> T + fn r#gen(&mut self) -> T where Standard: Distribution, { @@ -474,8 +478,8 @@ mod test { // Check equivalence for generated floats let mut array = [0f32; 2]; rng.fill(&mut array); - let gen: [f32; 2] = rng.random(); - assert_eq!(array, gen); + let arr2: [f32; 2] = rng.random(); + assert_eq!(array, arr2); } #[test] diff --git a/src/seq/index.rs b/src/seq/index.rs index 5bb1a7597f5..e66b5039883 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -282,10 +282,12 @@ where } } -/// Randomly sample exactly `amount` distinct indices from `0..length`, and -/// return them in an arbitrary order (there is no guarantee of shuffling or -/// ordering). The weights are to be provided by the input function `weights`, -/// which will be called once for each index. +/// Randomly sample exactly `amount` distinct indices from `0..length` +/// +/// Results are in arbitrary order (there is no guarantee of shuffling or +/// ordering). +/// +/// Function `weight` is called once for each index to provide weights. /// /// This method is used internally by the slice sampling methods, but it can /// sometimes be useful to have the indices themselves so this is provided as From bc3341185ee9fd63e63a7c3266f28478aa2ae5fd Mon Sep 17 00:00:00 2001 From: Benjamin Lieser Date: Thu, 3 Oct 2024 21:18:56 +0200 Subject: [PATCH 409/443] Make sure BTPE is not entered when np < 10 (#1484) --- rand_distr/CHANGELOG.md | 2 + rand_distr/src/binomial.rs | 483 ++++++++++++++++++++----------------- 2 files changed, 270 insertions(+), 215 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 51bde39e86b..93756eb7055 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - The `serde1` feature has been renamed `serde` (#1477) +- Fix panic in Binomial (#1484) +- Move some of the computations in Binomial from `sample` to `new` (#1484) ### Added - Add plots for `rand_distr` distributions to documentation (#1434) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 885d8b21c3f..3ee0f447b42 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -26,10 +26,6 @@ use rand::Rng; /// /// `f(k) = n!/(k! (n-k)!) p^k (1-p)^(n-k)` for `k >= 0`. /// -/// # Known issues -/// -/// See documentation of [`Binomial::new`]. -/// /// # Plot /// /// The following plot of the binomial distribution illustrates the @@ -50,10 +46,34 @@ use rand::Rng; #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Binomial { - /// Number of trials. + method: Method, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +enum Method { + Binv(Binv, bool), + Btpe(Btpe, bool), + Poisson(crate::poisson::KnuthMethod), + Constant(u64), +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct Binv { + r: f64, + s: f64, + a: f64, + n: u64, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct Btpe { n: u64, - /// Probability of success. p: f64, + m: i64, + p1: f64, } /// Error type returned from [`Binomial::new`]. @@ -82,13 +102,6 @@ impl std::error::Error for Error {} impl Binomial { /// Construct a new `Binomial` with the given shape parameters `n` (number /// of trials) and `p` (probability of success). - /// - /// # Known issues - /// - /// Although this method should return an [`Error`] on invalid parameters, - /// some (extreme) parameter combinations are known to return a [`Binomial`] - /// object which panics when [sampled](Distribution::sample). - /// See [#1378](https://github.com/rust-random/rand/issues/1378). pub fn new(n: u64, p: f64) -> Result { if !(p >= 0.0) { return Err(Error::ProbabilityTooSmall); @@ -96,33 +109,22 @@ impl Binomial { if !(p <= 1.0) { return Err(Error::ProbabilityTooLarge); } - Ok(Binomial { n, p }) - } -} - -/// Convert a `f64` to an `i64`, panicking on overflow. -fn f64_to_i64(x: f64) -> i64 { - assert!(x < (i64::MAX as f64)); - x as i64 -} -impl Distribution for Binomial { - #[allow(clippy::many_single_char_names)] // Same names as in the reference. - fn sample(&self, rng: &mut R) -> u64 { - // Handle these values directly. - if self.p == 0.0 { - return 0; - } else if self.p == 1.0 { - return self.n; + if p == 0.0 { + return Ok(Binomial { + method: Method::Constant(0), + }); } - // The binomial distribution is symmetrical with respect to p -> 1-p, - // k -> n-k switch p so that it is less than 0.5 - this allows for lower - // expected values we will just invert the result at the end - let p = if self.p <= 0.5 { self.p } else { 1.0 - self.p }; + if p == 1.0 { + return Ok(Binomial { + method: Method::Constant(n), + }); + } - let result; - let q = 1. - p; + // The binomial distribution is symmetrical with respect to p -> 1-p + let flipped = p > 0.5; + let p = if flipped { 1.0 - p } else { p }; // For small n * min(p, 1 - p), the BINV algorithm based on the inverse // transformation of the binomial distribution is efficient. Otherwise, @@ -136,204 +138,253 @@ impl Distribution for Binomial { // Ranlib uses 30, and GSL uses 14. const BINV_THRESHOLD: f64 = 10.; - // Same value as in GSL. - // It is possible for BINV to get stuck, so we break if x > BINV_MAX_X and try again. - // It would be safer to set BINV_MAX_X to self.n, but it is extremely unlikely to be relevant. - // When n*p < 10, so is n*p*q which is the variance, so a result > 110 would be 100 / sqrt(10) = 31 standard deviations away. - const BINV_MAX_X: u64 = 110; - - if (self.n as f64) * p < BINV_THRESHOLD && self.n <= (i32::MAX as u64) { - // Use the BINV algorithm. - let s = p / q; - let a = ((self.n + 1) as f64) * s; - - result = 'outer: loop { - let mut r = q.powi(self.n as i32); - let mut u: f64 = rng.random(); - let mut x = 0; - - while u > r { - u -= r; - x += 1; - if x > BINV_MAX_X { - continue 'outer; - } - r *= a / (x as f64) - s; - } - break x; + let np = n as f64 * p; + let method = if np < BINV_THRESHOLD { + let q = 1.0 - p; + if q == 1.0 { + // p is so small that this is extremely close to a Poisson distribution. + // The flipped case cannot occur here. + Method::Poisson(crate::poisson::KnuthMethod::new(np)) + } else { + let s = p / q; + Method::Binv( + Binv { + r: q.powf(n as f64), + s, + a: (n as f64 + 1.0) * s, + n, + }, + flipped, + ) } } else { - // Use the BTPE algorithm. - - // Threshold for using the squeeze algorithm. This can be freely - // chosen based on performance. Ranlib and GSL use 20. - const SQUEEZE_THRESHOLD: i64 = 20; - - // Step 0: Calculate constants as functions of `n` and `p`. - let n = self.n as f64; - let np = n * p; + let q = 1.0 - p; let npq = np * q; + let p1 = (2.195 * npq.sqrt() - 4.6 * q).floor() + 0.5; let f_m = np + p; let m = f64_to_i64(f_m); - // radius of triangle region, since height=1 also area of region - let p1 = (2.195 * npq.sqrt() - 4.6 * q).floor() + 0.5; - // tip of triangle - let x_m = (m as f64) + 0.5; - // left edge of triangle - let x_l = x_m - p1; - // right edge of triangle - let x_r = x_m + p1; - let c = 0.134 + 20.5 / (15.3 + (m as f64)); - // p1 + area of parallelogram region - let p2 = p1 * (1. + 2. * c); - - fn lambda(a: f64) -> f64 { - a * (1. + 0.5 * a) + Method::Btpe(Btpe { n, p, m, p1 }, flipped) + }; + Ok(Binomial { method }) + } +} + +/// Convert a `f64` to an `i64`, panicking on overflow. +fn f64_to_i64(x: f64) -> i64 { + assert!(x < (i64::MAX as f64)); + x as i64 +} + +fn binv(binv: Binv, flipped: bool, rng: &mut R) -> u64 { + // Same value as in GSL. + // It is possible for BINV to get stuck, so we break if x > BINV_MAX_X and try again. + // It would be safer to set BINV_MAX_X to self.n, but it is extremely unlikely to be relevant. + // When n*p < 10, so is n*p*q which is the variance, so a result > 110 would be 100 / sqrt(10) = 31 standard deviations away. + const BINV_MAX_X: u64 = 110; + + let sample = 'outer: loop { + let mut r = binv.r; + let mut u: f64 = rng.random(); + let mut x = 0; + + while u > r { + u -= r; + x += 1; + if x > BINV_MAX_X { + continue 'outer; } + r *= binv.a / (x as f64) - binv.s; + } + break x; + }; - let lambda_l = lambda((f_m - x_l) / (f_m - x_l * p)); - let lambda_r = lambda((x_r - f_m) / (x_r * q)); - // p1 + area of left tail - let p3 = p2 + c / lambda_l; - // p1 + area of right tail - let p4 = p3 + c / lambda_r; - - // return value - let mut y: i64; - - let gen_u = Uniform::new(0., p4).unwrap(); - let gen_v = Uniform::new(0., 1.).unwrap(); - - loop { - // Step 1: Generate `u` for selecting the region. If region 1 is - // selected, generate a triangularly distributed variate. - let u = gen_u.sample(rng); - let mut v = gen_v.sample(rng); - if !(u > p1) { - y = f64_to_i64(x_m - p1 * v + u); - break; - } + if flipped { + binv.n - sample + } else { + sample + } +} - if !(u > p2) { - // Step 2: Region 2, parallelograms. Check if region 2 is - // used. If so, generate `y`. - let x = x_l + (u - p1) / c; - v = v * c + 1.0 - (x - x_m).abs() / p1; - if v > 1. { - continue; - } else { - y = f64_to_i64(x); - } - } else if !(u > p3) { - // Step 3: Region 3, left exponential tail. - y = f64_to_i64(x_l + v.ln() / lambda_l); - if y < 0 { - continue; - } else { - v *= (u - p2) * lambda_l; - } - } else { - // Step 4: Region 4, right exponential tail. - y = f64_to_i64(x_r - v.ln() / lambda_r); - if y > 0 && (y as u64) > self.n { - continue; - } else { - v *= (u - p3) * lambda_r; - } - } +#[allow(clippy::many_single_char_names)] // Same names as in the reference. +fn btpe(btpe: Btpe, flipped: bool, rng: &mut R) -> u64 { + // Threshold for using the squeeze algorithm. This can be freely + // chosen based on performance. Ranlib and GSL use 20. + const SQUEEZE_THRESHOLD: i64 = 20; + + // Step 0: Calculate constants as functions of `n` and `p`. + let n = btpe.n as f64; + let np = n * btpe.p; + let q = 1. - btpe.p; + let npq = np * q; + let f_m = np + btpe.p; + let m = btpe.m; + // radius of triangle region, since height=1 also area of region + let p1 = btpe.p1; + // tip of triangle + let x_m = (m as f64) + 0.5; + // left edge of triangle + let x_l = x_m - p1; + // right edge of triangle + let x_r = x_m + p1; + let c = 0.134 + 20.5 / (15.3 + (m as f64)); + // p1 + area of parallelogram region + let p2 = p1 * (1. + 2. * c); + + fn lambda(a: f64) -> f64 { + a * (1. + 0.5 * a) + } - // Step 5: Acceptance/rejection comparison. - - // Step 5.0: Test for appropriate method of evaluating f(y). - let k = (y - m).abs(); - if !(k > SQUEEZE_THRESHOLD && (k as f64) < 0.5 * npq - 1.) { - // Step 5.1: Evaluate f(y) via the recursive relationship. Start the - // search from the mode. - let s = p / q; - let a = s * (n + 1.); - let mut f = 1.0; - match m.cmp(&y) { - Ordering::Less => { - let mut i = m; - loop { - i += 1; - f *= a / (i as f64) - s; - if i == y { - break; - } - } - } - Ordering::Greater => { - let mut i = y; - loop { - i += 1; - f /= a / (i as f64) - s; - if i == m { - break; - } - } + let lambda_l = lambda((f_m - x_l) / (f_m - x_l * btpe.p)); + let lambda_r = lambda((x_r - f_m) / (x_r * q)); + + let p3 = p2 + c / lambda_l; + + let p4 = p3 + c / lambda_r; + + // return value + let mut y: i64; + + let gen_u = Uniform::new(0., p4).unwrap(); + let gen_v = Uniform::new(0., 1.).unwrap(); + + loop { + // Step 1: Generate `u` for selecting the region. If region 1 is + // selected, generate a triangularly distributed variate. + let u = gen_u.sample(rng); + let mut v = gen_v.sample(rng); + if !(u > p1) { + y = f64_to_i64(x_m - p1 * v + u); + break; + } + + if !(u > p2) { + // Step 2: Region 2, parallelograms. Check if region 2 is + // used. If so, generate `y`. + let x = x_l + (u - p1) / c; + v = v * c + 1.0 - (x - x_m).abs() / p1; + if v > 1. { + continue; + } else { + y = f64_to_i64(x); + } + } else if !(u > p3) { + // Step 3: Region 3, left exponential tail. + y = f64_to_i64(x_l + v.ln() / lambda_l); + if y < 0 { + continue; + } else { + v *= (u - p2) * lambda_l; + } + } else { + // Step 4: Region 4, right exponential tail. + y = f64_to_i64(x_r - v.ln() / lambda_r); + if y > 0 && (y as u64) > btpe.n { + continue; + } else { + v *= (u - p3) * lambda_r; + } + } + + // Step 5: Acceptance/rejection comparison. + + // Step 5.0: Test for appropriate method of evaluating f(y). + let k = (y - m).abs(); + if !(k > SQUEEZE_THRESHOLD && (k as f64) < 0.5 * npq - 1.) { + // Step 5.1: Evaluate f(y) via the recursive relationship. Start the + // search from the mode. + let s = btpe.p / q; + let a = s * (n + 1.); + let mut f = 1.0; + match m.cmp(&y) { + Ordering::Less => { + let mut i = m; + loop { + i += 1; + f *= a / (i as f64) - s; + if i == y { + break; } - Ordering::Equal => {} } - if v > f { - continue; - } else { - break; - } - } - - // Step 5.2: Squeezing. Check the value of ln(v) against upper and - // lower bound of ln(f(y)). - let k = k as f64; - let rho = (k / npq) * ((k * (k / 3. + 0.625) + 1. / 6.) / npq + 0.5); - let t = -0.5 * k * k / npq; - let alpha = v.ln(); - if alpha < t - rho { - break; } - if alpha > t + rho { - continue; + Ordering::Greater => { + let mut i = y; + loop { + i += 1; + f /= a / (i as f64) - s; + if i == m { + break; + } + } } + Ordering::Equal => {} + } + if v > f { + continue; + } else { + break; + } + } - // Step 5.3: Final acceptance/rejection test. - let x1 = (y + 1) as f64; - let f1 = (m + 1) as f64; - let z = (f64_to_i64(n) + 1 - m) as f64; - let w = (f64_to_i64(n) - y + 1) as f64; + // Step 5.2: Squeezing. Check the value of ln(v) against upper and + // lower bound of ln(f(y)). + let k = k as f64; + let rho = (k / npq) * ((k * (k / 3. + 0.625) + 1. / 6.) / npq + 0.5); + let t = -0.5 * k * k / npq; + let alpha = v.ln(); + if alpha < t - rho { + break; + } + if alpha > t + rho { + continue; + } - fn stirling(a: f64) -> f64 { - let a2 = a * a; - (13860. - (462. - (132. - (99. - 140. / a2) / a2) / a2) / a2) / a / 166320. - } + // Step 5.3: Final acceptance/rejection test. + let x1 = (y + 1) as f64; + let f1 = (m + 1) as f64; + let z = (f64_to_i64(n) + 1 - m) as f64; + let w = (f64_to_i64(n) - y + 1) as f64; - if alpha - > x_m * (f1 / x1).ln() - + (n - (m as f64) + 0.5) * (z / w).ln() - + ((y - m) as f64) * (w * p / (x1 * q)).ln() - // We use the signs from the GSL implementation, which are - // different than the ones in the reference. According to - // the GSL authors, the new signs were verified to be - // correct by one of the original designers of the - // algorithm. - + stirling(f1) - + stirling(z) - - stirling(x1) - - stirling(w) - { - continue; - } + fn stirling(a: f64) -> f64 { + let a2 = a * a; + (13860. - (462. - (132. - (99. - 140. / a2) / a2) / a2) / a2) / a / 166320. + } - break; - } - assert!(y >= 0); - result = y as u64; + if alpha + > x_m * (f1 / x1).ln() + + (n - (m as f64) + 0.5) * (z / w).ln() + + ((y - m) as f64) * (w * btpe.p / (x1 * q)).ln() + // We use the signs from the GSL implementation, which are + // different than the ones in the reference. According to + // the GSL authors, the new signs were verified to be + // correct by one of the original designers of the + // algorithm. + + stirling(f1) + + stirling(z) + - stirling(x1) + - stirling(w) + { + continue; } - // Invert the result for p < 0.5. - if p != self.p { - self.n - result - } else { - result + break; + } + assert!(y >= 0); + let y = y as u64; + + if flipped { + btpe.n - y + } else { + y + } +} + +impl Distribution for Binomial { + fn sample(&self, rng: &mut R) -> u64 { + match self.method { + Method::Binv(binv_para, flipped) => binv(binv_para, flipped, rng), + Method::Btpe(btpe_para, flipped) => btpe(btpe_para, flipped, rng), + Method::Poisson(poisson) => poisson.sample(rng) as u64, + Method::Constant(c) => c, } } } @@ -371,6 +422,8 @@ mod test { test_binomial_mean_and_variance(40, 0.5, &mut rng); test_binomial_mean_and_variance(20, 0.7, &mut rng); test_binomial_mean_and_variance(20, 0.5, &mut rng); + test_binomial_mean_and_variance(1 << 61, 1e-17, &mut rng); + test_binomial_mean_and_variance(u64::MAX, 1e-19, &mut rng); } #[test] From d2eb51bc291d555cc559d337b34ec0c502927397 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Mon, 7 Oct 2024 01:29:52 -0700 Subject: [PATCH 410/443] Remove some unsafe, update to zerocopy 0.8.0 (#1502) --- Cargo.toml | 2 +- rand_core/Cargo.toml | 2 +- rand_core/src/impls.rs | 4 ++-- src/rng.rs | 16 +++------------- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9924c007234..2e860f7ffed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ rand_core = { path = "rand_core", version = "=0.9.0-alpha.1", default-features = log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.1", default-features = false, optional = true } -zerocopy = { version = "0.7.33", default-features = false, features = ["simd"] } +zerocopy = { version = "0.8.0", default-features = false, features = ["simd"] } [dev-dependencies] rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index dc4084d69ae..5d7ec72c4cf 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -32,4 +32,4 @@ serde = ["dep:serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } getrandom = { version = "0.2", optional = true } -zerocopy = { version = "0.7.33", default-features = false } +zerocopy = { version = "0.8.0", default-features = false } diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 34230e15822..267049c7870 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -19,7 +19,7 @@ use crate::RngCore; use core::cmp::min; -use zerocopy::AsBytes; +use zerocopy::{Immutable, IntoBytes}; /// Implement `next_u64` via `next_u32`, little-endian order. pub fn next_u64_via_u32(rng: &mut R) -> u64 { @@ -53,7 +53,7 @@ pub fn fill_bytes_via_next(rng: &mut R, dest: &mut [u8]) { } } -trait Observable: AsBytes + Copy { +trait Observable: IntoBytes + Immutable + Copy { fn to_le(self) -> Self; } impl Observable for u32 { diff --git a/src/rng.rs b/src/rng.rs index 7c9e887a2d7..45361a00ab4 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -12,8 +12,8 @@ use crate::distr::uniform::{SampleRange, SampleUniform}; use crate::distr::{self, Distribution, Standard}; use core::num::Wrapping; -use core::{mem, slice}; use rand_core::RngCore; +use zerocopy::IntoBytes; /// User-level interface for RNGs /// @@ -374,12 +374,7 @@ macro_rules! impl_fill { #[inline(never)] // in micro benchmarks, this improves performance fn fill(&mut self, rng: &mut R) { if self.len() > 0 { - rng.fill_bytes(unsafe { - slice::from_raw_parts_mut(self.as_mut_ptr() - as *mut u8, - mem::size_of_val(self) - ) - }); + rng.fill_bytes(self.as_mut_bytes()); for x in self { *x = x.to_le(); } @@ -391,12 +386,7 @@ macro_rules! impl_fill { #[inline(never)] fn fill(&mut self, rng: &mut R) { if self.len() > 0 { - rng.fill_bytes(unsafe { - slice::from_raw_parts_mut(self.as_mut_ptr() - as *mut u8, - self.len() * mem::size_of::<$t>() - ) - }); + rng.fill_bytes(self.as_mut_bytes()); for x in self { *x = Wrapping(x.0.to_le()); } From d1f961c4be38f4147ac1adb3c2bfb2745a9fde6b Mon Sep 17 00:00:00 2001 From: Arthur Silva Date: Mon, 7 Oct 2024 13:12:40 +0200 Subject: [PATCH 411/443] Improve SmallRng initialization performance (#1482) --- CHANGELOG.md | 1 + benches/benches/generators.rs | 58 +++++++++++++++++++++++++++++++++- src/rngs/small.rs | 3 +- src/rngs/xoshiro128plusplus.rs | 35 ++++++++++++++++---- src/rngs/xoshiro256plusplus.rs | 42 ++++++++++++++++++++---- 5 files changed, 123 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3300b9ad9fe..a09c14c5631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) - Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) +- Improve SmallRng initialization performance (#1482) - Implement `Distribution` for `Poisson` (#1498) - Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) - Rename `Rng::gen_iter` to `random_iter` (#1500) diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index 580d9897551..bea4f60a6b4 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -19,7 +19,7 @@ use rand_pcg::{Pcg32, Pcg64, Pcg64Dxsm, Pcg64Mcg}; criterion_group!( name = benches; config = Criterion::default(); - targets = gen_bytes, gen_u32, gen_u64, init_gen, reseeding_bytes + targets = gen_bytes, gen_u32, gen_u64, init_gen, init_from_u64, init_from_seed, reseeding_bytes ); criterion_main!(benches); @@ -133,6 +133,62 @@ pub fn init_gen(c: &mut Criterion) { bench::(&mut g, "chacha12"); bench::(&mut g, "chacha20"); bench::(&mut g, "std"); + bench::(&mut g, "small"); + + g.finish() +} + +pub fn init_from_u64(c: &mut Criterion) { + let mut g = c.benchmark_group("init_from_u64"); + g.warm_up_time(Duration::from_millis(500)); + g.measurement_time(Duration::from_millis(1000)); + + fn bench(g: &mut BenchmarkGroup, name: &str) { + g.bench_function(name, |b| { + let mut rng = Pcg32::from_os_rng(); + let seed = rng.random(); + b.iter(|| R::seed_from_u64(black_box(seed))); + }); + } + + bench::(&mut g, "pcg32"); + bench::(&mut g, "pcg64"); + bench::(&mut g, "pcg64mcg"); + bench::(&mut g, "pcg64dxsm"); + bench::(&mut g, "chacha8"); + bench::(&mut g, "chacha12"); + bench::(&mut g, "chacha20"); + bench::(&mut g, "std"); + bench::(&mut g, "small"); + + g.finish() +} + +pub fn init_from_seed(c: &mut Criterion) { + let mut g = c.benchmark_group("init_from_seed"); + g.warm_up_time(Duration::from_millis(500)); + g.measurement_time(Duration::from_millis(1000)); + + fn bench(g: &mut BenchmarkGroup, name: &str) + where + rand::distr::Standard: Distribution<::Seed>, + { + g.bench_function(name, |b| { + let mut rng = Pcg32::from_os_rng(); + let seed = rng.random(); + b.iter(|| R::from_seed(black_box(seed.clone()))); + }); + } + + bench::(&mut g, "pcg32"); + bench::(&mut g, "pcg64"); + bench::(&mut g, "pcg64mcg"); + bench::(&mut g, "pcg64dxsm"); + bench::(&mut g, "chacha8"); + bench::(&mut g, "chacha12"); + bench::(&mut g, "chacha20"); + bench::(&mut g, "std"); + bench::(&mut g, "small"); g.finish() } diff --git a/src/rngs/small.rs b/src/rngs/small.rs index ea7df062842..cfc6b0c9888 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -83,7 +83,8 @@ impl SeedableRng for SmallRng { #[inline(always)] fn from_seed(seed: Self::Seed) -> Self { - // With MSRV >= 1.77: let seed = *seed.first_chunk().unwrap(); + // This is for compatibility with 32-bit platforms where Rng::Seed has a different seed size + // With MSRV >= 1.77: let seed = *seed.first_chunk().unwrap() const LEN: usize = core::mem::size_of::<::Seed>(); let seed = (&seed[..LEN]).try_into().unwrap(); SmallRng(Rng::from_seed(seed)) diff --git a/src/rngs/xoshiro128plusplus.rs b/src/rngs/xoshiro128plusplus.rs index 44e4222c888..6bcc33ba5db 100644 --- a/src/rngs/xoshiro128plusplus.rs +++ b/src/rngs/xoshiro128plusplus.rs @@ -33,29 +33,36 @@ impl SeedableRng for Xoshiro128PlusPlus { /// mapped to a different seed. #[inline] fn from_seed(seed: [u8; 16]) -> Xoshiro128PlusPlus { - if seed.iter().all(|&x| x == 0) { - return Self::seed_from_u64(0); - } let mut state = [0; 4]; read_u32_into(&seed, &mut state); + // Check for zero on aligned integers for better code generation. + // Furtermore, seed_from_u64(0) will expand to a constant when optimized. + if state.iter().all(|&x| x == 0) { + return Self::seed_from_u64(0); + } Xoshiro128PlusPlus { s: state } } /// Create a new `Xoshiro128PlusPlus` from a `u64` seed. /// /// This uses the SplitMix64 generator internally. + #[inline] fn seed_from_u64(mut state: u64) -> Self { const PHI: u64 = 0x9e3779b97f4a7c15; - let mut seed = Self::Seed::default(); - for chunk in seed.as_mut().chunks_mut(8) { + let mut s = [0; 4]; + for i in s.chunks_exact_mut(2) { state = state.wrapping_add(PHI); let mut z = state; z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); z = z ^ (z >> 31); - chunk.copy_from_slice(&z.to_le_bytes()); + i[0] = z as u32; + i[1] = (z >> 32) as u32; } - Self::from_seed(seed) + // By using a non-zero PHI we are guaranteed to generate a non-zero state + // Thus preventing a recursion between from_seed and seed_from_u64. + debug_assert_ne!(s, [0; 4]); + Xoshiro128PlusPlus { s } } } @@ -113,4 +120,18 @@ mod tests { assert_eq!(rng.next_u32(), e); } } + + #[test] + fn stable_seed_from_u64() { + // We don't guarantee value-stability for SmallRng but this + // could influence keeping stability whenever possible (e.g. after optimizations). + let mut rng = Xoshiro128PlusPlus::seed_from_u64(0); + let expected = [ + 1179900579, 1938959192, 3089844957, 3657088315, 1015453891, 479942911, 3433842246, + 669252886, 3985671746, 2737205563, + ]; + for &e in &expected { + assert_eq!(rng.next_u32(), e); + } + } } diff --git a/src/rngs/xoshiro256plusplus.rs b/src/rngs/xoshiro256plusplus.rs index b356ff510c8..b1c022e0f13 100644 --- a/src/rngs/xoshiro256plusplus.rs +++ b/src/rngs/xoshiro256plusplus.rs @@ -33,29 +33,35 @@ impl SeedableRng for Xoshiro256PlusPlus { /// mapped to a different seed. #[inline] fn from_seed(seed: [u8; 32]) -> Xoshiro256PlusPlus { - if seed.iter().all(|&x| x == 0) { - return Self::seed_from_u64(0); - } let mut state = [0; 4]; read_u64_into(&seed, &mut state); + // Check for zero on aligned integers for better code generation. + // Furtermore, seed_from_u64(0) will expand to a constant when optimized. + if state.iter().all(|&x| x == 0) { + return Self::seed_from_u64(0); + } Xoshiro256PlusPlus { s: state } } /// Create a new `Xoshiro256PlusPlus` from a `u64` seed. /// /// This uses the SplitMix64 generator internally. + #[inline] fn seed_from_u64(mut state: u64) -> Self { const PHI: u64 = 0x9e3779b97f4a7c15; - let mut seed = Self::Seed::default(); - for chunk in seed.as_mut().chunks_mut(8) { + let mut s = [0; 4]; + for i in s.iter_mut() { state = state.wrapping_add(PHI); let mut z = state; z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); z = z ^ (z >> 31); - chunk.copy_from_slice(&z.to_le_bytes()); + *i = z; } - Self::from_seed(seed) + // By using a non-zero PHI we are guaranteed to generate a non-zero state + // Thus preventing a recursion between from_seed and seed_from_u64. + debug_assert_ne!(s, [0; 4]); + Xoshiro256PlusPlus { s } } } @@ -126,4 +132,26 @@ mod tests { assert_eq!(rng.next_u64(), e); } } + + #[test] + fn stable_seed_from_u64() { + // We don't guarantee value-stability for SmallRng but this + // could influence keeping stability whenever possible (e.g. after optimizations). + let mut rng = Xoshiro256PlusPlus::seed_from_u64(0); + let expected = [ + 5987356902031041503, + 7051070477665621255, + 6633766593972829180, + 211316841551650330, + 9136120204379184874, + 379361710973160858, + 15813423377499357806, + 15596884590815070553, + 5439680534584881407, + 1369371744833522710, + ]; + for &e in &expected { + assert_eq!(rng.next_u64(), e); + } + } } From 9c2787de73da3096eb1db7ee3bcfd05d10b948c1 Mon Sep 17 00:00:00 2001 From: Benjamin Lieser Date: Tue, 8 Oct 2024 10:57:47 +0200 Subject: [PATCH 412/443] Cdf testing with Kolmogorov Smirnov (#1494) --- rand_distr/CHANGELOG.md | 1 + rand_distr/tests/cdf.rs | 197 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 rand_distr/tests/cdf.rs diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 93756eb7055..a19641752f7 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `serde1` feature has been renamed `serde` (#1477) - Fix panic in Binomial (#1484) - Move some of the computations in Binomial from `sample` to `new` (#1484) +- Add Kolmogorov Smirnov test for sampling of `Normal` and `Binomial` (#1494) ### Added - Add plots for `rand_distr` distributions to documentation (#1434) diff --git a/rand_distr/tests/cdf.rs b/rand_distr/tests/cdf.rs new file mode 100644 index 00000000000..71b808d241c --- /dev/null +++ b/rand_distr/tests/cdf.rs @@ -0,0 +1,197 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::f64; + +use num_traits::AsPrimitive; +use rand::SeedableRng; +use rand_distr::{Distribution, Normal}; +use special::Beta; +use special::Primitive; + +// [1] Nonparametric Goodness-of-Fit Tests for Discrete Null Distributions +// by Taylor B. Arnold and John W. Emerson +// http://www.stat.yale.edu/~jay/EmersonMaterials/DiscreteGOF.pdf + +/// Empirical Cumulative Distribution Function (ECDF) +struct Ecdf { + sorted_samples: Vec, +} + +impl Ecdf { + fn new(mut samples: Vec) -> Self { + samples.sort_by(|a, b| a.partial_cmp(b).unwrap()); + Self { + sorted_samples: samples, + } + } + + /// Returns the step points of the ECDF + /// The ECDF is a step function that increases by 1/n at each sample point + /// The function is continuous from the right, so we give the bigger value at the step points + /// First point is (-inf, 0.0), last point is (max(samples), 1.0) + fn step_points(&self) -> Vec<(f64, f64)> { + let mut points = Vec::with_capacity(self.sorted_samples.len() + 1); + let mut last = f64::NEG_INFINITY; + let mut count = 0; + let n = self.sorted_samples.len() as f64; + for &x in &self.sorted_samples { + if x != last { + points.push((last, count as f64 / n)); + last = x; + } + count += 1; + } + points.push((last, count as f64 / n)); + points + } +} + +fn kolmogorov_smirnov_statistic_continuous(ecdf: Ecdf, cdf: impl Fn(f64) -> f64) -> f64 { + // We implement equation (3) from [1] + + let mut max_diff: f64 = 0.; + + let step_points = ecdf.step_points(); // x_i in the paper + for i in 1..step_points.len() { + let (x_i, f_i) = step_points[i]; + let (_, f_i_1) = step_points[i - 1]; + let cdf_i = cdf(x_i); + let max_1 = (cdf_i - f_i).abs(); + let max_2 = (cdf_i - f_i_1).abs(); + + max_diff = max_diff.max(max_1).max(max_2); + } + max_diff +} + +fn kolmogorov_smirnov_statistic_discrete(ecdf: Ecdf, cdf: impl Fn(i64) -> f64) -> f64 { + // We implement equation (4) from [1] + + let mut max_diff: f64 = 0.; + + let step_points = ecdf.step_points(); // x_i in the paper + for i in 1..step_points.len() { + let (x_i, f_i) = step_points[i]; + let (_, f_i_1) = step_points[i - 1]; + let max_1 = (cdf(x_i as i64) - f_i).abs(); + let max_2 = (cdf(x_i as i64 - 1) - f_i_1).abs(); // -1 is the same as -epsilon, because we have integer support + + max_diff = max_diff.max(max_1).max(max_2); + } + max_diff +} + +const SAMPLE_SIZE: u64 = 1_000_000; + +fn critical_value() -> f64 { + // If the sampler is correct, we expect less than 0.001 false positives (alpha = 0.001). + // Passing this does not prove that the sampler is correct but is a good indication. + 1.95 / (SAMPLE_SIZE as f64).sqrt() +} + +fn sample_ecdf(seed: u64, dist: impl Distribution) -> Ecdf +where + T: AsPrimitive, +{ + let mut rng = rand::rngs::SmallRng::seed_from_u64(seed); + let samples = (0..SAMPLE_SIZE) + .map(|_| dist.sample(&mut rng).as_()) + .collect(); + Ecdf::new(samples) +} + +/// Tests a distribution against an analytical CDF. +/// The CDF has to be continuous. +pub fn test_continuous(seed: u64, dist: impl Distribution, cdf: impl Fn(f64) -> f64) { + let ecdf = sample_ecdf(seed, dist); + let ks_statistic = kolmogorov_smirnov_statistic_continuous(ecdf, cdf); + + let critical_value = critical_value(); + + println!("KS statistic: {}", ks_statistic); + println!("Critical value: {}", critical_value); + assert!(ks_statistic < critical_value); +} + +/// Tests a distribution over integers against an analytical CDF. +/// The analytical CDF must not have jump points which are not integers. +pub fn test_discrete>( + seed: u64, + dist: impl Distribution, + cdf: impl Fn(i64) -> f64, +) { + let ecdf = sample_ecdf(seed, dist); + let ks_statistic = kolmogorov_smirnov_statistic_discrete(ecdf, cdf); + + // This critical value is bigger than it could be for discrete distributions, but because of large sample sizes this should not matter too much + let critical_value = critical_value(); + + println!("KS statistic: {}", ks_statistic); + println!("Critical value: {}", critical_value); + assert!(ks_statistic < critical_value); +} + +fn normal_cdf(x: f64, mean: f64, std_dev: f64) -> f64 { + 0.5 * ((mean - x) / (std_dev * f64::consts::SQRT_2)).erfc() +} + +#[test] +fn normal() { + let parameters = [ + (0.0, 1.0), + (0.0, 0.1), + (1.0, 10.0), + (1.0, 100.0), + (-1.0, 0.00001), + (-1.0, 0.0000001), + ]; + + for (seed, (mean, std_dev)) in parameters.into_iter().enumerate() { + test_continuous(seed as u64, Normal::new(mean, std_dev).unwrap(), |x| { + normal_cdf(x, mean, std_dev) + }); + } +} + +fn binomial_cdf(k: i64, p: f64, n: u64) -> f64 { + if k < 0 { + return 0.0; + } + let k = k as u64; + if k >= n { + return 1.0; + } + + let a = (n - k) as f64; + let b = k as f64 + 1.0; + + let q = 1.0 - p; + + let ln_beta_ab = a.ln_beta(b); + + q.inc_beta(a, b, ln_beta_ab) +} + +#[test] +fn binomial() { + let parameters = [ + (0.5, 10), + (0.5, 100), + (0.1, 10), + (0.0000001, 1000000), + (0.0000001, 10), + (0.9999, 2), + ]; + + for (seed, (p, n)) in parameters.into_iter().enumerate() { + test_discrete(seed as u64, rand_distr::Binomial::new(n, p).unwrap(), |k| { + binomial_cdf(k, p, n) + }); + } +} From 0fba9401c4a301d8edd99bacdb751a0600b55a38 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 9 Oct 2024 13:27:07 +0300 Subject: [PATCH 413/443] rand_core: add blanket impl of TryRngCore for RngCore (#1499) This PR removes the hacky `impl_try_rng_from_rng_core` and `impl_try_crypto_rng_from_crypto_rng` macros and replaces them with blanket impls of `TryRngCore` for `RngCore` and `TryCryptoRng` for `CryptoRng`. This change means that `TryRngCore`/`TryCryptoRng` no longer can have blanket impls for `&mut R` and `Box`. But I think it should be tolerable since most users will be using `RngCore`/`CryptoRng`, which have blanket impl for `DerefMut` (it covers both `&mut R` and `Box`). --- benches/benches/seq_choose.rs | 8 +-- benches/benches/shuffle.rs | 2 +- benches/benches/uniform.rs | 4 +- benches/benches/uniform_float.rs | 4 +- benches/benches/weighted.rs | 2 +- rand_chacha/src/chacha.rs | 2 - rand_core/src/blanket_impls.rs | 88 -------------------------------- rand_core/src/block.rs | 8 +-- rand_core/src/impls.rs | 54 -------------------- rand_core/src/lib.rs | 73 +++++++++++++++++++++----- src/prelude.rs | 2 +- src/rngs/mock.rs | 2 - src/rngs/small.rs | 2 - src/rngs/std.rs | 2 - src/rngs/thread.rs | 2 - src/rngs/xoshiro128plusplus.rs | 2 - src/rngs/xoshiro256plusplus.rs | 2 - 17 files changed, 75 insertions(+), 184 deletions(-) delete mode 100644 rand_core/src/blanket_impls.rs diff --git a/benches/benches/seq_choose.rs b/benches/benches/seq_choose.rs index 58c4f894eaf..82345eeb5b9 100644 --- a/benches/benches/seq_choose.rs +++ b/benches/benches/seq_choose.rs @@ -20,7 +20,7 @@ criterion_main!(benches); pub fn bench(c: &mut Criterion) { c.bench_function("seq_slice_choose_1_of_100", |b| { - let mut rng = Pcg32::from_rng(thread_rng()); + let mut rng = Pcg32::from_rng(&mut thread_rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); let x = black_box(&mut buf); @@ -32,7 +32,7 @@ pub fn bench(c: &mut Criterion) { for (amount, len) in lens { let name = format!("seq_slice_choose_multiple_{}_of_{}", amount, len); c.bench_function(name.as_str(), |b| { - let mut rng = Pcg32::from_rng(thread_rng()); + let mut rng = Pcg32::from_rng(&mut thread_rng()); let mut buf = [0i32; 1000]; rng.fill(&mut buf); let x = black_box(&buf[..len]); @@ -53,7 +53,7 @@ pub fn bench(c: &mut Criterion) { } c.bench_function("seq_iter_choose_multiple_10_of_100", |b| { - let mut rng = Pcg32::from_rng(thread_rng()); + let mut rng = Pcg32::from_rng(&mut thread_rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); let x = black_box(&buf); @@ -61,7 +61,7 @@ pub fn bench(c: &mut Criterion) { }); c.bench_function("seq_iter_choose_multiple_fill_10_of_100", |b| { - let mut rng = Pcg32::from_rng(thread_rng()); + let mut rng = Pcg32::from_rng(&mut thread_rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); let x = black_box(&buf); diff --git a/benches/benches/shuffle.rs b/benches/benches/shuffle.rs index 106d12e0abc..abfdc8ffc1b 100644 --- a/benches/benches/shuffle.rs +++ b/benches/benches/shuffle.rs @@ -20,7 +20,7 @@ criterion_main!(benches); pub fn bench(c: &mut Criterion) { c.bench_function("seq_shuffle_100", |b| { - let mut rng = Pcg32::from_rng(thread_rng()); + let mut rng = Pcg32::from_rng(&mut thread_rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); let x = black_box(&mut buf); diff --git a/benches/benches/uniform.rs b/benches/benches/uniform.rs index 78eb066eac7..059c21de1ff 100644 --- a/benches/benches/uniform.rs +++ b/benches/benches/uniform.rs @@ -23,7 +23,7 @@ const N_RESAMPLES: usize = 10_000; macro_rules! sample { ($R:ty, $T:ty, $U:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($R), "single"), |b| { - let mut rng = <$R>::from_rng(thread_rng()); + let mut rng = <$R>::from_rng(&mut thread_rng()); let x = rng.random::<$U>(); let bits = (<$T>::BITS / 2); let mask = (1 as $U).wrapping_neg() >> bits; @@ -35,7 +35,7 @@ macro_rules! sample { }); $g.bench_function(BenchmarkId::new(stringify!($R), "distr"), |b| { - let mut rng = <$R>::from_rng(thread_rng()); + let mut rng = <$R>::from_rng(&mut thread_rng()); let x = rng.random::<$U>(); let bits = (<$T>::BITS / 2); let mask = (1 as $U).wrapping_neg() >> bits; diff --git a/benches/benches/uniform_float.rs b/benches/benches/uniform_float.rs index f33a2b729d3..ce524d1a31e 100644 --- a/benches/benches/uniform_float.rs +++ b/benches/benches/uniform_float.rs @@ -27,7 +27,7 @@ const N_RESAMPLES: usize = 10_000; macro_rules! single_random { ($R:ty, $T:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { - let mut rng = <$R>::from_rng(thread_rng()); + let mut rng = <$R>::from_rng(&mut thread_rng()); let (mut low, mut high); loop { low = <$T>::from_bits(rng.random()); @@ -63,7 +63,7 @@ fn single_random(c: &mut Criterion) { macro_rules! distr_random { ($R:ty, $T:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { - let mut rng = <$R>::from_rng(thread_rng()); + let mut rng = <$R>::from_rng(&mut thread_rng()); let dist = loop { let low = <$T>::from_bits(rng.random()); let high = <$T>::from_bits(rng.random()); diff --git a/benches/benches/weighted.rs b/benches/benches/weighted.rs index 21e66ebee3b..a1e15a16b85 100644 --- a/benches/benches/weighted.rs +++ b/benches/benches/weighted.rs @@ -53,7 +53,7 @@ pub fn bench(c: &mut Criterion) { c.bench_function(name.as_str(), |b| { let length = black_box(length); let amount = black_box(amount); - let mut rng = SmallRng::from_rng(thread_rng()); + let mut rng = SmallRng::from_rng(&mut thread_rng()); b.iter(|| sample_weighted(&mut rng, length, |idx| (1 + (idx % 100)) as u32, amount)) }); } diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 297095f6486..831858090d9 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -257,8 +257,6 @@ macro_rules! chacha_impl { impl CryptoRng for $ChaChaXRng {} - rand_core::impl_try_crypto_rng_from_crypto_rng!($ChaChaXRng); - impl From<$ChaChaXCore> for $ChaChaXRng { fn from(core: $ChaChaXCore) -> Self { $ChaChaXRng { diff --git a/rand_core/src/blanket_impls.rs b/rand_core/src/blanket_impls.rs deleted file mode 100644 index 754382f5179..00000000000 --- a/rand_core/src/blanket_impls.rs +++ /dev/null @@ -1,88 +0,0 @@ -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - -use crate::{CryptoRng, RngCore, TryCryptoRng, TryRngCore}; - -impl<'a, R: RngCore + ?Sized> RngCore for &'a mut R { - #[inline(always)] - fn next_u32(&mut self) -> u32 { - R::next_u32(self) - } - - #[inline(always)] - fn next_u64(&mut self) -> u64 { - R::next_u64(self) - } - - #[inline(always)] - fn fill_bytes(&mut self, dst: &mut [u8]) { - R::fill_bytes(self, dst) - } -} - -impl<'a, R: CryptoRng + ?Sized> CryptoRng for &'a mut R {} - -impl<'a, R: TryRngCore + ?Sized> TryRngCore for &'a mut R { - type Error = R::Error; - - #[inline(always)] - fn try_next_u32(&mut self) -> Result { - R::try_next_u32(self) - } - - #[inline(always)] - fn try_next_u64(&mut self) -> Result { - R::try_next_u64(self) - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { - R::try_fill_bytes(self, dst) - } -} - -impl<'a, R: TryCryptoRng + ?Sized> TryCryptoRng for &'a mut R {} - -#[cfg(feature = "alloc")] -impl RngCore for Box { - #[inline(always)] - fn next_u32(&mut self) -> u32 { - R::next_u32(self) - } - - #[inline(always)] - fn next_u64(&mut self) -> u64 { - R::next_u64(self) - } - - #[inline(always)] - fn fill_bytes(&mut self, dest: &mut [u8]) { - R::fill_bytes(self, dest) - } -} - -#[cfg(feature = "alloc")] -impl CryptoRng for Box {} - -#[cfg(feature = "alloc")] -impl TryRngCore for Box { - type Error = R::Error; - - #[inline(always)] - fn try_next_u32(&mut self) -> Result { - R::try_next_u32(self) - } - - #[inline(always)] - fn try_next_u64(&mut self) -> Result { - R::try_next_u64(self) - } - - #[inline(always)] - fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { - R::try_fill_bytes(self, dst) - } -} - -#[cfg(feature = "alloc")] -impl TryCryptoRng for Box {} diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index 36072ba355b..c0243d4c132 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -250,12 +250,12 @@ impl SeedableRng for BlockRng { } #[inline(always)] - fn from_rng(rng: impl RngCore) -> Self { + fn from_rng(rng: &mut impl RngCore) -> Self { Self::new(R::from_rng(rng)) } #[inline(always)] - fn try_from_rng(rng: S) -> Result { + fn try_from_rng(rng: &mut S) -> Result { R::try_from_rng(rng).map(Self::new) } } @@ -415,12 +415,12 @@ impl SeedableRng for BlockRng64 { } #[inline(always)] - fn from_rng(rng: impl RngCore) -> Self { + fn from_rng(rng: &mut impl RngCore) -> Self { Self::new(R::from_rng(rng)) } #[inline(always)] - fn try_from_rng(rng: S) -> Result { + fn try_from_rng(rng: &mut S) -> Result { R::try_from_rng(rng).map(Self::new) } } diff --git a/rand_core/src/impls.rs b/rand_core/src/impls.rs index 267049c7870..584a4c16f10 100644 --- a/rand_core/src/impls.rs +++ b/rand_core/src/impls.rs @@ -160,60 +160,6 @@ pub fn next_u64_via_fill(rng: &mut R) -> u64 { u64::from_le_bytes(buf) } -/// Implement [`TryRngCore`][crate::TryRngCore] for a type implementing [`RngCore`]. -/// -/// Ideally, `rand_core` would define blanket impls for this, but they conflict with blanket impls -/// for `&mut R` and `Box`, so until specialziation is stabilized, implementer crates -/// have to implement `TryRngCore` directly. -#[macro_export] -macro_rules! impl_try_rng_from_rng_core { - ($t:ty) => { - impl $crate::TryRngCore for $t { - type Error = core::convert::Infallible; - - #[inline] - fn try_next_u32(&mut self) -> Result { - Ok($crate::RngCore::next_u32(self)) - } - - #[inline] - fn try_next_u64(&mut self) -> Result { - Ok($crate::RngCore::next_u64(self)) - } - - #[inline] - fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { - $crate::RngCore::fill_bytes(self, dst); - Ok(()) - } - } - }; -} - -/// Implement [`TryCryptoRng`] and [`TryRngCore`] for a type implementing [`CryptoRng`]. -/// -/// Ideally, `rand_core` would define blanket impls for this, but they conflict with blanket impls -/// for `&mut R` and `Box`, so until specialziation is stabilized, implementer crates -/// have to implement `TryRngCore` and `TryCryptoRng` directly. -/// -/// [`TryCryptoRng`]: crate::TryCryptoRng -/// [`TryRngCore`]: crate::TryRngCore -/// [`CryptoRng`]: crate::CryptoRng -#[macro_export] -macro_rules! impl_try_crypto_rng_from_crypto_rng { - ($t:ty) => { - $crate::impl_try_rng_from_rng_core!($t); - - impl $crate::TryCryptoRng for $t {} - - /// Check at compile time that `$t` implements `CryptoRng` - const _: () = { - const fn check_crypto_rng_impl() {} - check_crypto_rng_impl::<$t>(); - }; - }; -} - #[cfg(test)] mod test { use super::*; diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 39e95d95db2..7eea6a310aa 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -40,9 +40,8 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -use core::fmt; +use core::{fmt, ops::DerefMut}; -mod blanket_impls; pub mod block; pub mod impls; pub mod le; @@ -83,9 +82,9 @@ pub use os::OsRng; /// in this trait directly, then use the helper functions from the /// [`impls`] module to implement the other methods. /// -/// Implementors of [`RngCore`] SHOULD also implement the [`TryRngCore`] -/// trait with the `Error` associated type being equal to [`Infallible`]. -/// It can be done using the [`impl_try_rng_from_rng_core!`] macro. +/// Note that implementors of [`RngCore`] also automatically implement +/// the [`TryRngCore`] trait with the `Error` associated type being +/// equal to [`Infallible`]. /// /// It is recommended that implementations also implement: /// @@ -125,8 +124,6 @@ pub use os::OsRng; /// impls::fill_bytes_via_next(self, dst) /// } /// } -/// -/// rand_core::impl_try_rng_from_rng_core!(CountingRng); /// ``` /// /// [`rand::Rng`]: https://docs.rs/rand/latest/rand/trait.Rng.html @@ -162,13 +159,29 @@ pub trait RngCore { fn fill_bytes(&mut self, dst: &mut [u8]); } +impl RngCore for T +where + T::Target: RngCore, +{ + #[inline] + fn next_u32(&mut self) -> u32 { + self.deref_mut().next_u32() + } + + #[inline] + fn next_u64(&mut self) -> u64 { + self.deref_mut().next_u64() + } + + #[inline] + fn fill_bytes(&mut self, dst: &mut [u8]) { + self.deref_mut().fill_bytes(dst); + } +} + /// A marker trait used to indicate that an [`RngCore`] implementation is /// supposed to be cryptographically secure. /// -/// Implementors of [`CryptoRng`] SHOULD also implement the [`TryCryptoRng`] -/// trait with the `Error` associated type being equal to [`Infallible`]. -/// It can be done using the [`impl_try_crypto_rng_from_crypto_rng!`] macro. -/// /// *Cryptographically secure generators*, also known as *CSPRNGs*, should /// satisfy an additional properties over other generators: given the first /// *k* bits of an algorithm's output @@ -187,10 +200,15 @@ pub trait RngCore { /// Note also that use of a `CryptoRng` does not protect against other /// weaknesses such as seeding from a weak entropy source or leaking state. /// +/// Note that implementors of [`CryptoRng`] also automatically implement +/// the [`TryCryptoRng`] trait. +/// /// [`BlockRngCore`]: block::BlockRngCore /// [`Infallible`]: core::convert::Infallible pub trait CryptoRng: RngCore {} +impl CryptoRng for T where T::Target: CryptoRng {} + /// A potentially fallible version of [`RngCore`]. /// /// This trait is primarily used for IO-based generators such as [`OsRng`]. @@ -233,26 +251,54 @@ pub trait TryRngCore { } } +// Note that, unfortunately, this blanket impl prevents us from implementing +// `TryRngCore` for types which can be dereferenced to `TryRngCore`, i.e. `TryRngCore` +// will not be automatically implemented for `&mut R`, `Box`, etc. +impl TryRngCore for R { + type Error = core::convert::Infallible; + + #[inline] + fn try_next_u32(&mut self) -> Result { + Ok(self.next_u32()) + } + + #[inline] + fn try_next_u64(&mut self) -> Result { + Ok(self.next_u64()) + } + + #[inline] + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { + self.fill_bytes(dst); + Ok(()) + } +} + /// A marker trait used to indicate that a [`TryRngCore`] implementation is /// supposed to be cryptographically secure. /// /// See [`CryptoRng`] docs for more information about cryptographically secure generators. pub trait TryCryptoRng: TryRngCore {} +impl TryCryptoRng for R {} + /// Wrapper around [`TryRngCore`] implementation which implements [`RngCore`] /// by panicking on potential errors. #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)] pub struct UnwrapErr(pub R); impl RngCore for UnwrapErr { + #[inline] fn next_u32(&mut self) -> u32 { self.0.try_next_u32().unwrap() } + #[inline] fn next_u64(&mut self) -> u64 { self.0.try_next_u64().unwrap() } + #[inline] fn fill_bytes(&mut self, dst: &mut [u8]) { self.0.try_fill_bytes(dst).unwrap() } @@ -416,7 +462,7 @@ pub trait SeedableRng: Sized { /// (in prior versions this was not required). /// /// [`rand`]: https://docs.rs/rand - fn from_rng(mut rng: impl RngCore) -> Self { + fn from_rng(rng: &mut impl RngCore) -> Self { let mut seed = Self::Seed::default(); rng.fill_bytes(seed.as_mut()); Self::from_seed(seed) @@ -425,7 +471,7 @@ pub trait SeedableRng: Sized { /// Create a new PRNG seeded from a potentially fallible `Rng`. /// /// See [`from_rng`][SeedableRng::from_rng] docs for more information. - fn try_from_rng(mut rng: R) -> Result { + fn try_from_rng(rng: &mut R) -> Result { let mut seed = Self::Seed::default(); rng.try_fill_bytes(seed.as_mut())?; Ok(Self::from_seed(seed)) @@ -492,6 +538,7 @@ pub struct RngReadAdapter<'a, R: TryRngCore + ?Sized> { #[cfg(feature = "std")] impl std::io::Read for RngReadAdapter<'_, R> { + #[inline] fn read(&mut self, buf: &mut [u8]) -> Result { self.inner.try_fill_bytes(buf).map_err(|err| { std::io::Error::new(std::io::ErrorKind::Other, std::format!("RNG error: {err}")) diff --git a/src/prelude.rs b/src/prelude.rs index 37b703742df..803c641ad90 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -14,7 +14,7 @@ //! //! ``` //! use rand::prelude::*; -//! # let mut r = StdRng::from_rng(thread_rng()); +//! # let mut r = StdRng::from_rng(&mut thread_rng()); //! # let _: f32 = r.random(); //! ``` diff --git a/src/rngs/mock.rs b/src/rngs/mock.rs index 7238fbc8819..dc0bd764a6b 100644 --- a/src/rngs/mock.rs +++ b/src/rngs/mock.rs @@ -75,8 +75,6 @@ impl RngCore for StepRng { } } -rand_core::impl_try_rng_from_rng_core!(StepRng); - #[cfg(test)] mod tests { #[cfg(any(feature = "alloc", feature = "serde"))] diff --git a/src/rngs/small.rs b/src/rngs/small.rs index cfc6b0c9888..e2b1d211253 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -113,8 +113,6 @@ impl RngCore for SmallRng { } } -rand_core::impl_try_rng_from_rng_core!(SmallRng); - impl SmallRng { /// Construct an instance seeded from the thread-local RNG /// diff --git a/src/rngs/std.rs b/src/rngs/std.rs index 7483becb71f..d59291b8567 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -91,8 +91,6 @@ impl SeedableRng for StdRng { impl CryptoRng for StdRng {} -rand_core::impl_try_crypto_rng_from_crypto_rng!(StdRng); - #[cfg(test)] mod test { use crate::rngs::StdRng; diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 161b5d6efcb..45b353088e3 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -170,8 +170,6 @@ impl RngCore for ThreadRng { impl CryptoRng for ThreadRng {} -rand_core::impl_try_crypto_rng_from_crypto_rng!(ThreadRng); - #[cfg(test)] mod test { #[test] diff --git a/src/rngs/xoshiro128plusplus.rs b/src/rngs/xoshiro128plusplus.rs index 6bcc33ba5db..69fe7ca9202 100644 --- a/src/rngs/xoshiro128plusplus.rs +++ b/src/rngs/xoshiro128plusplus.rs @@ -99,8 +99,6 @@ impl RngCore for Xoshiro128PlusPlus { } } -rand_core::impl_try_rng_from_rng_core!(Xoshiro128PlusPlus); - #[cfg(test)] mod tests { use super::Xoshiro128PlusPlus; diff --git a/src/rngs/xoshiro256plusplus.rs b/src/rngs/xoshiro256plusplus.rs index b1c022e0f13..7b39c6109a7 100644 --- a/src/rngs/xoshiro256plusplus.rs +++ b/src/rngs/xoshiro256plusplus.rs @@ -101,8 +101,6 @@ impl RngCore for Xoshiro256PlusPlus { } } -rand_core::impl_try_rng_from_rng_core!(Xoshiro256PlusPlus); - #[cfg(test)] mod tests { use super::Xoshiro256PlusPlus; From f5185d91fae77b1c75344c22036dbaceaeaaba1e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 11 Oct 2024 08:51:21 +0100 Subject: [PATCH 414/443] =?UTF-8?q?thread=5Frng()=20=E2=86=92=20rand::rng(?= =?UTF-8?q?)=20(#1506)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename `rand::thread_rng()` → `rand::rng()` - Remove `thread_rng()` and `random()` from the prelude --- CHANGELOG.md | 2 ++ README.md | 6 ++-- SECURITY.md | 4 +-- benches/benches/array.rs | 16 ++++----- benches/benches/bool.rs | 14 ++++---- benches/benches/generators.rs | 6 ++-- benches/benches/seq_choose.rs | 8 ++--- benches/benches/shuffle.rs | 2 +- benches/benches/uniform.rs | 4 +-- benches/benches/uniform_float.rs | 4 +-- benches/benches/weighted.rs | 6 ++-- examples/monte-carlo.rs | 2 +- examples/monty-hall.rs | 2 +- rand_chacha/src/lib.rs | 4 +-- rand_core/src/lib.rs | 4 +-- rand_distr/src/beta.rs | 2 +- rand_distr/src/binomial.rs | 2 +- rand_distr/src/cauchy.rs | 2 +- rand_distr/src/chi_squared.rs | 2 +- rand_distr/src/dirichlet.rs | 2 +- rand_distr/src/exponential.rs | 4 +-- rand_distr/src/fisher_f.rs | 2 +- rand_distr/src/frechet.rs | 2 +- rand_distr/src/gamma.rs | 2 +- rand_distr/src/geometric.rs | 4 +-- rand_distr/src/gumbel.rs | 2 +- rand_distr/src/hypergeometric.rs | 2 +- rand_distr/src/inverse_gaussian.rs | 2 +- rand_distr/src/normal.rs | 10 +++--- rand_distr/src/normal_inverse_gaussian.rs | 2 +- rand_distr/src/pareto.rs | 2 +- rand_distr/src/pert.rs | 2 +- rand_distr/src/poisson.rs | 2 +- rand_distr/src/skew_normal.rs | 2 +- rand_distr/src/student_t.rs | 2 +- rand_distr/src/triangular.rs | 2 +- rand_distr/src/unit_ball.rs | 2 +- rand_distr/src/unit_circle.rs | 2 +- rand_distr/src/unit_disc.rs | 2 +- rand_distr/src/unit_sphere.rs | 2 +- rand_distr/src/weibull.rs | 2 +- rand_distr/src/weighted_alias.rs | 2 +- rand_distr/src/weighted_tree.rs | 2 +- rand_distr/src/zeta.rs | 2 +- rand_distr/src/zipf.rs | 2 +- rand_pcg/src/lib.rs | 4 +-- src/distr/bernoulli.rs | 2 +- src/distr/distribution.rs | 6 ++-- src/distr/float.rs | 8 ++--- src/distr/other.rs | 8 ++--- src/distr/slice.rs | 4 +-- src/distr/uniform.rs | 14 ++++---- src/distr/weighted_index.rs | 2 +- src/lib.rs | 23 ++++++++---- src/prelude.rs | 5 +-- src/rng.rs | 37 ++++++++++--------- src/rngs/mod.rs | 3 +- src/rngs/small.rs | 6 ++-- src/rngs/thread.rs | 43 +++++++++++++---------- src/seq/iterator.rs | 4 +-- src/seq/slice.rs | 16 ++++----- 61 files changed, 174 insertions(+), 167 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a09c14c5631..99a34b76964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Implement `Distribution` for `Poisson` (#1498) - Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) - Rename `Rng::gen_iter` to `random_iter` (#1500) +- Rename `rand::thread_rng()` to `rand::rng()`, and remove from the prelude (#1506) +- Remove `rand::random()` from the prelude (#1506) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/README.md b/README.md index 25341ac2d03..1dbe4c55f12 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Rand is a Rust library supporting random generators: - A standard RNG trait: [`rand_core::RngCore`](https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html) - Fast implementations of the best-in-class [cryptographic](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and [non-cryptographic](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) -- [`rand::thread_rng`](https://docs.rs/rand/latest/rand/fn.thread_rng.html) is an asymtotically-fast, reasonably secure generator available on all `std` targets +- [`rand::rng`](https://docs.rs/rand/latest/rand/fn.rng.html) is an asymtotically-fast, reasonably secure generator available on all `std` targets - Secure seeding via the [`getrandom` crate](https://crates.io/crates/getrandom) Supporting random value generation and random processes: @@ -78,7 +78,7 @@ Rand is built with these features enabled by default: - `alloc` (implied by `std`) enables functionality requiring an allocator - `getrandom` (implied by `std`) is an optional dependency providing the code behind `rngs::OsRng` -- `std_rng` enables inclusion of `StdRng`, `thread_rng` +- `std_rng` enables inclusion of `StdRng`, `ThreadRng` Optionally, the following dependencies can be enabled: @@ -98,7 +98,7 @@ experimental `simd_support` feature. Rand supports limited functionality in `no_std` mode (enabled via `default-features = false`). In this case, `OsRng` and `from_os_rng` are unavailable (unless `getrandom` is enabled), large parts of `seq` are -unavailable (unless `alloc` is enabled), and `thread_rng` is unavailable. +unavailable (unless `alloc` is enabled), and `ThreadRng` is unavailable. ## Portability and platform support diff --git a/SECURITY.md b/SECURITY.md index a48752976c7..356fbe879de 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -52,12 +52,12 @@ Explanation of exceptions: - Jitter: `JitterRng` is used as an entropy source when the primary source fails; this source may not be secure against side-channel attacks, see #699. - ISAAC: the [ISAAC](https://burtleburtle.net/bob/rand/isaacafa.html) RNG used - to implement `thread_rng` is difficult to analyse and thus cannot provide + to implement `ThreadRng` is difficult to analyse and thus cannot provide strong assertions of security. ## Known issues -In `rand` version 0.3 (0.3.18 and later), if `OsRng` fails, `thread_rng` is +In `rand` version 0.3 (0.3.18 and later), if `OsRng` fails, `ThreadRng` is seeded from the system time in an insecure manner. ## Reporting a Vulnerability diff --git a/benches/benches/array.rs b/benches/benches/array.rs index c8d99dab493..bf328bc7a9b 100644 --- a/benches/benches/array.rs +++ b/benches/benches/array.rs @@ -26,7 +26,7 @@ pub fn bench(c: &mut Criterion) { g.bench_function("u16_iter_repeat", |b| { use core::iter; - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| { let v: Vec = iter::repeat(()).map(|()| rng.random()).take(512).collect(); v @@ -34,7 +34,7 @@ pub fn bench(c: &mut Criterion) { }); g.bench_function("u16_sample_iter", |b| { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| { let v: Vec = Standard.sample_iter(&mut rng).take(512).collect(); v @@ -42,7 +42,7 @@ pub fn bench(c: &mut Criterion) { }); g.bench_function("u16_gen_array", |b| { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| { let v: [u16; 512] = rng.random(); v @@ -50,7 +50,7 @@ pub fn bench(c: &mut Criterion) { }); g.bench_function("u16_fill", |b| { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); let mut buf = [0u16; 512]; b.iter(|| { rng.fill(&mut buf[..]); @@ -60,7 +60,7 @@ pub fn bench(c: &mut Criterion) { g.bench_function("u64_iter_repeat", |b| { use core::iter; - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| { let v: Vec = iter::repeat(()).map(|()| rng.random()).take(128).collect(); v @@ -68,7 +68,7 @@ pub fn bench(c: &mut Criterion) { }); g.bench_function("u64_sample_iter", |b| { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| { let v: Vec = Standard.sample_iter(&mut rng).take(128).collect(); v @@ -76,7 +76,7 @@ pub fn bench(c: &mut Criterion) { }); g.bench_function("u64_gen_array", |b| { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| { let v: [u64; 128] = rng.random(); v @@ -84,7 +84,7 @@ pub fn bench(c: &mut Criterion) { }); g.bench_function("u64_fill", |b| { - let mut rng = Pcg64Mcg::from_rng(&mut thread_rng()); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); let mut buf = [0u64; 128]; b.iter(|| { rng.fill(&mut buf[..]); diff --git a/benches/benches/bool.rs b/benches/benches/bool.rs index e659ce5ba56..0584c1ddfc6 100644 --- a/benches/benches/bool.rs +++ b/benches/benches/bool.rs @@ -27,41 +27,41 @@ pub fn bench(c: &mut Criterion) { g.measurement_time(core::time::Duration::from_millis(1000)); g.bench_function("standard", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); b.iter(|| rng.sample::(rand::distr::Standard)) }); g.bench_function("const", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); b.iter(|| rng.gen_bool(0.18)) }); g.bench_function("var", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let p = rng.random(); b.iter(|| rng.gen_bool(p)) }); g.bench_function("ratio_const", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); b.iter(|| rng.gen_ratio(2, 3)) }); g.bench_function("ratio_var", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let d = rng.gen_range(1..=100); let n = rng.gen_range(0..=d); b.iter(|| rng.gen_ratio(n, d)); }); g.bench_function("bernoulli_const", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let d = Bernoulli::new(0.18).unwrap(); b.iter(|| rng.sample(d)) }); g.bench_function("bernoulli_var", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let p = rng.random(); let d = Bernoulli::new(p).unwrap(); b.iter(|| rng.sample(d)) diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index bea4f60a6b4..a3370c2357a 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -50,7 +50,7 @@ pub fn gen_bytes(c: &mut Criterion) { bench(&mut g, "std", StdRng::from_os_rng()); bench(&mut g, "small", SmallRng::from_thread_rng()); bench(&mut g, "os", UnwrapErr(OsRng)); - bench(&mut g, "thread", thread_rng()); + bench(&mut g, "thread", rand::rng()); g.finish() } @@ -79,7 +79,7 @@ pub fn gen_u32(c: &mut Criterion) { bench(&mut g, "std", StdRng::from_os_rng()); bench(&mut g, "small", SmallRng::from_thread_rng()); bench(&mut g, "os", UnwrapErr(OsRng)); - bench(&mut g, "thread", thread_rng()); + bench(&mut g, "thread", rand::rng()); g.finish() } @@ -108,7 +108,7 @@ pub fn gen_u64(c: &mut Criterion) { bench(&mut g, "std", StdRng::from_os_rng()); bench(&mut g, "small", SmallRng::from_thread_rng()); bench(&mut g, "os", UnwrapErr(OsRng)); - bench(&mut g, "thread", thread_rng()); + bench(&mut g, "thread", rand::rng()); g.finish() } diff --git a/benches/benches/seq_choose.rs b/benches/benches/seq_choose.rs index 82345eeb5b9..a664b915016 100644 --- a/benches/benches/seq_choose.rs +++ b/benches/benches/seq_choose.rs @@ -20,7 +20,7 @@ criterion_main!(benches); pub fn bench(c: &mut Criterion) { c.bench_function("seq_slice_choose_1_of_100", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); let x = black_box(&mut buf); @@ -32,7 +32,7 @@ pub fn bench(c: &mut Criterion) { for (amount, len) in lens { let name = format!("seq_slice_choose_multiple_{}_of_{}", amount, len); c.bench_function(name.as_str(), |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let mut buf = [0i32; 1000]; rng.fill(&mut buf); let x = black_box(&buf[..len]); @@ -53,7 +53,7 @@ pub fn bench(c: &mut Criterion) { } c.bench_function("seq_iter_choose_multiple_10_of_100", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); let x = black_box(&buf); @@ -61,7 +61,7 @@ pub fn bench(c: &mut Criterion) { }); c.bench_function("seq_iter_choose_multiple_fill_10_of_100", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); let x = black_box(&buf); diff --git a/benches/benches/shuffle.rs b/benches/benches/shuffle.rs index abfdc8ffc1b..c2f37daaeab 100644 --- a/benches/benches/shuffle.rs +++ b/benches/benches/shuffle.rs @@ -20,7 +20,7 @@ criterion_main!(benches); pub fn bench(c: &mut Criterion) { c.bench_function("seq_shuffle_100", |b| { - let mut rng = Pcg32::from_rng(&mut thread_rng()); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let mut buf = [0i32; 100]; rng.fill(&mut buf); let x = black_box(&mut buf); diff --git a/benches/benches/uniform.rs b/benches/benches/uniform.rs index 059c21de1ff..ab1b0ed4149 100644 --- a/benches/benches/uniform.rs +++ b/benches/benches/uniform.rs @@ -23,7 +23,7 @@ const N_RESAMPLES: usize = 10_000; macro_rules! sample { ($R:ty, $T:ty, $U:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($R), "single"), |b| { - let mut rng = <$R>::from_rng(&mut thread_rng()); + let mut rng = <$R>::from_rng(&mut rand::rng()); let x = rng.random::<$U>(); let bits = (<$T>::BITS / 2); let mask = (1 as $U).wrapping_neg() >> bits; @@ -35,7 +35,7 @@ macro_rules! sample { }); $g.bench_function(BenchmarkId::new(stringify!($R), "distr"), |b| { - let mut rng = <$R>::from_rng(&mut thread_rng()); + let mut rng = <$R>::from_rng(&mut rand::rng()); let x = rng.random::<$U>(); let bits = (<$T>::BITS / 2); let mask = (1 as $U).wrapping_neg() >> bits; diff --git a/benches/benches/uniform_float.rs b/benches/benches/uniform_float.rs index ce524d1a31e..03a434fc228 100644 --- a/benches/benches/uniform_float.rs +++ b/benches/benches/uniform_float.rs @@ -27,7 +27,7 @@ const N_RESAMPLES: usize = 10_000; macro_rules! single_random { ($R:ty, $T:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { - let mut rng = <$R>::from_rng(&mut thread_rng()); + let mut rng = <$R>::from_rng(&mut rand::rng()); let (mut low, mut high); loop { low = <$T>::from_bits(rng.random()); @@ -63,7 +63,7 @@ fn single_random(c: &mut Criterion) { macro_rules! distr_random { ($R:ty, $T:ty, $g:expr) => { $g.bench_function(BenchmarkId::new(stringify!($T), stringify!($R)), |b| { - let mut rng = <$R>::from_rng(&mut thread_rng()); + let mut rng = <$R>::from_rng(&mut rand::rng()); let dist = loop { let low = <$T>::from_bits(rng.random()); let high = <$T>::from_bits(rng.random()); diff --git a/benches/benches/weighted.rs b/benches/benches/weighted.rs index a1e15a16b85..d7af914736b 100644 --- a/benches/benches/weighted.rs +++ b/benches/benches/weighted.rs @@ -20,7 +20,7 @@ criterion_main!(benches); pub fn bench(c: &mut Criterion) { c.bench_function("weighted_index_creation", |b| { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let weights = black_box([1u32, 2, 4, 0, 5, 1, 7, 1, 2, 3, 4, 5, 6, 7]); b.iter(|| { let distr = WeightedIndex::new(weights.to_vec()).unwrap(); @@ -29,7 +29,7 @@ pub fn bench(c: &mut Criterion) { }); c.bench_function("weighted_index_modification", |b| { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let weights = black_box([1u32, 2, 3, 0, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]); let mut distr = WeightedIndex::new(weights.to_vec()).unwrap(); b.iter(|| { @@ -53,7 +53,7 @@ pub fn bench(c: &mut Criterion) { c.bench_function(name.as_str(), |b| { let length = black_box(length); let amount = black_box(amount); - let mut rng = SmallRng::from_rng(&mut thread_rng()); + let mut rng = SmallRng::from_rng(&mut rand::rng()); b.iter(|| sample_weighted(&mut rng, length, |idx| (1 + (idx % 100)) as u32, amount)) }); } diff --git a/examples/monte-carlo.rs b/examples/monte-carlo.rs index 0f50a4aeb0d..d5b898f17f0 100644 --- a/examples/monte-carlo.rs +++ b/examples/monte-carlo.rs @@ -27,7 +27,7 @@ use rand::distr::{Distribution, Uniform}; fn main() { let range = Uniform::new(-1.0f64, 1.0).unwrap(); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let total = 1_000_000; let mut in_circle = 0; diff --git a/examples/monty-hall.rs b/examples/monty-hall.rs index a6fcd04e802..0a6d033739c 100644 --- a/examples/monty-hall.rs +++ b/examples/monty-hall.rs @@ -77,7 +77,7 @@ fn main() { // The estimation will be more accurate with more simulations let num_simulations = 10000; - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let random_door = Uniform::new(0u32, 3).unwrap(); let (mut switch_wins, mut switch_losses) = (0, 0); diff --git a/rand_chacha/src/lib.rs b/rand_chacha/src/lib.rs index ba72b1b5b60..e7a3d25eb1a 100644 --- a/rand_chacha/src/lib.rs +++ b/rand_chacha/src/lib.rs @@ -41,7 +41,7 @@ //! let rng = ChaCha12Rng::from_os_rng(); //! # let _: ChaCha12Rng = rng; //! ``` -//! 2. **From a master generator.** This could be [`rand::thread_rng`] +//! 2. **From a master generator.** This could be [`rand::rng`] //! (effectively a fresh seed without the need for a syscall on each usage) //! or a deterministic generator such as [`ChaCha20Rng`]. //! Beware that should a weak master generator be used, correlations may be @@ -74,7 +74,7 @@ //! [`RngCore`]: rand_core::RngCore //! [`SeedableRng`]: rand_core::SeedableRng //! [`SeedableRng::from_os_rng`]: rand_core::SeedableRng::from_os_rng -//! [`rand::thread_rng`]: https://docs.rs/rand/latest/rand/fn.thread_rng.html +//! [`rand::rng`]: https://docs.rs/rand/latest/rand/fn.rng.html //! [`rand::Rng`]: https://docs.rs/rand/latest/rand/trait.Rng.html #![doc( diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 7eea6a310aa..4e75faf25b5 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -487,7 +487,7 @@ pub trait SeedableRng: Sized { /// /// In case the overhead of using [`getrandom`] to seed *many* PRNGs is an /// issue, one may prefer to seed from a local PRNG, e.g. - /// `from_rng(thread_rng()).unwrap()`. + /// `from_rng(rand::rng()).unwrap()`. /// /// # Panics /// @@ -508,7 +508,7 @@ pub trait SeedableRng: Sized { /// /// In case the overhead of using [`getrandom`] to seed *many* PRNGs is an /// issue, one may prefer to seed from a local PRNG, e.g. - /// `from_rng(&mut thread_rng()).unwrap()`. + /// `from_rng(&mut rand::rng()).unwrap()`. /// /// [`getrandom`]: https://docs.rs/getrandom #[cfg(feature = "getrandom")] diff --git a/rand_distr/src/beta.rs b/rand_distr/src/beta.rs index da3106a5050..4dc297cfd50 100644 --- a/rand_distr/src/beta.rs +++ b/rand_distr/src/beta.rs @@ -73,7 +73,7 @@ struct BC { /// use rand_distr::{Distribution, Beta}; /// /// let beta = Beta::new(2.0, 5.0).unwrap(); -/// let v = beta.sample(&mut rand::thread_rng()); +/// let v = beta.sample(&mut rand::rng()); /// println!("{} is from a Beta(2, 5) distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 3ee0f447b42..d6dfceae777 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -40,7 +40,7 @@ use rand::Rng; /// use rand_distr::{Binomial, Distribution}; /// /// let bin = Binomial::new(20, 0.3).unwrap(); -/// let v = bin.sample(&mut rand::thread_rng()); +/// let v = bin.sample(&mut rand::rng()); /// println!("{} is from a binomial distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index e19c415df8a..4042b4fbf70 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -45,7 +45,7 @@ use rand::Rng; /// use rand_distr::{Cauchy, Distribution}; /// /// let cau = Cauchy::new(2.0, 5.0).unwrap(); -/// let v = cau.sample(&mut rand::thread_rng()); +/// let v = cau.sample(&mut rand::rng()); /// println!("{} is from a Cauchy(2, 5) distribution", v); /// ``` /// diff --git a/rand_distr/src/chi_squared.rs b/rand_distr/src/chi_squared.rs index c5cf41236c5..409a78bb311 100644 --- a/rand_distr/src/chi_squared.rs +++ b/rand_distr/src/chi_squared.rs @@ -41,7 +41,7 @@ use serde::{Deserialize, Serialize}; /// use rand_distr::{ChiSquared, Distribution}; /// /// let chi = ChiSquared::new(11.0).unwrap(); -/// let v = chi.sample(&mut rand::thread_rng()); +/// let v = chi.sample(&mut rand::rng()); /// println!("{} is from a χ²(11) distribution", v) /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index c7a6eda92d9..8ef5c31e84d 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -211,7 +211,7 @@ where /// use rand_distr::Dirichlet; /// /// let dirichlet = Dirichlet::new([1.0, 2.0, 3.0]).unwrap(); -/// let samples = dirichlet.sample(&mut rand::thread_rng()); +/// let samples = dirichlet.sample(&mut rand::rng()); /// println!("{:?} is from a Dirichlet([1.0, 2.0, 3.0]) distribution", samples); /// ``` #[cfg_attr(feature = "serde_with", serde_as)] diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index 1d23757190b..6d61015a8c1 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -34,7 +34,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Exp1; /// -/// let val: f64 = thread_rng().sample(Exp1); +/// let val: f64 = rand::rng().sample(Exp1); /// println!("{}", val); /// ``` /// @@ -116,7 +116,7 @@ impl Distribution for Exp1 { /// use rand_distr::{Exp, Distribution}; /// /// let exp = Exp::new(2.0).unwrap(); -/// let v = exp.sample(&mut rand::thread_rng()); +/// let v = exp.sample(&mut rand::rng()); /// println!("{} is from a Exp(2) distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/fisher_f.rs b/rand_distr/src/fisher_f.rs index 532b3237d7e..9c2c13cf64f 100644 --- a/rand_distr/src/fisher_f.rs +++ b/rand_distr/src/fisher_f.rs @@ -34,7 +34,7 @@ use serde::{Deserialize, Serialize}; /// use rand_distr::{FisherF, Distribution}; /// /// let f = FisherF::new(2.0, 32.0).unwrap(); -/// let v = f.sample(&mut rand::thread_rng()); +/// let v = f.sample(&mut rand::rng()); /// println!("{} is from an F(2, 32) distribution", v) /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/frechet.rs b/rand_distr/src/frechet.rs index 72dd6032dbe..feecd603fb5 100644 --- a/rand_distr/src/frechet.rs +++ b/rand_distr/src/frechet.rs @@ -40,7 +40,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Frechet; /// -/// let val: f64 = thread_rng().sample(Frechet::new(0.0, 1.0, 1.0).unwrap()); +/// let val: f64 = rand::rng().sample(Frechet::new(0.0, 1.0, 1.0).unwrap()); /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 8edda3a1abc..0fc6b756df3 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -47,7 +47,7 @@ use serde::{Deserialize, Serialize}; /// use rand_distr::{Distribution, Gamma}; /// /// let gamma = Gamma::new(2.0, 5.0).unwrap(); -/// let v = gamma.sample(&mut rand::thread_rng()); +/// let v = gamma.sample(&mut rand::rng()); /// println!("{} is from a Gamma(2, 5) distribution", v); /// ``` /// diff --git a/rand_distr/src/geometric.rs b/rand_distr/src/geometric.rs index b6c899e0e78..74d30a4459a 100644 --- a/rand_distr/src/geometric.rs +++ b/rand_distr/src/geometric.rs @@ -35,7 +35,7 @@ use rand::Rng; /// use rand_distr::{Geometric, Distribution}; /// /// let geo = Geometric::new(0.25).unwrap(); -/// let v = geo.sample(&mut rand::thread_rng()); +/// let v = geo.sample(&mut rand::rng()); /// println!("{} is from a Geometric(0.25) distribution", v); /// ``` #[derive(Copy, Clone, Debug, PartialEq)] @@ -168,7 +168,7 @@ impl Distribution for Geometric { /// use rand::prelude::*; /// use rand_distr::StandardGeometric; /// -/// let v = StandardGeometric.sample(&mut thread_rng()); +/// let v = StandardGeometric.sample(&mut rand::rng()); /// println!("{} is from a Geometric(0.5) distribution", v); /// ``` /// diff --git a/rand_distr/src/gumbel.rs b/rand_distr/src/gumbel.rs index 7ed326268b4..f420a52df84 100644 --- a/rand_distr/src/gumbel.rs +++ b/rand_distr/src/gumbel.rs @@ -38,7 +38,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Gumbel; /// -/// let val: f64 = thread_rng().sample(Gumbel::new(0.0, 1.0).unwrap()); +/// let val: f64 = rand::rng().sample(Gumbel::new(0.0, 1.0).unwrap()); /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index d17af2fdb76..c1f1d4ef234 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -54,7 +54,7 @@ enum SamplingMethod { /// use rand_distr::{Distribution, Hypergeometric}; /// /// let hypergeo = Hypergeometric::new(60, 24, 7).unwrap(); -/// let v = hypergeo.sample(&mut rand::thread_rng()); +/// let v = hypergeo.sample(&mut rand::rng()); /// println!("{} is from a hypergeometric distribution", v); /// ``` #[derive(Copy, Clone, Debug, PartialEq)] diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 06a9b72e858..8614a15e668 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -44,7 +44,7 @@ impl std::error::Error for Error {} /// use rand_distr::{InverseGaussian, Distribution}; /// /// let inv_gauss = InverseGaussian::new(1.0, 2.0).unwrap(); -/// let v = inv_gauss.sample(&mut rand::thread_rng()); +/// let v = inv_gauss.sample(&mut rand::rng()); /// println!("{} is from a inverse Gaussian(1, 2) distribution", v); /// ``` #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index ab95cad74e6..330c1ec2d6f 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -32,7 +32,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::StandardNormal; /// -/// let val: f64 = thread_rng().sample(StandardNormal); +/// let val: f64 = rand::rng().sample(StandardNormal); /// println!("{}", val); /// ``` /// @@ -130,7 +130,7 @@ impl Distribution for StandardNormal { /// /// // mean 2, standard deviation 3 /// let normal = Normal::new(2.0, 3.0).unwrap(); -/// let v = normal.sample(&mut rand::thread_rng()); +/// let v = normal.sample(&mut rand::rng()); /// println!("{} is from a N(2, 9) distribution", v) /// ``` /// @@ -215,7 +215,7 @@ where /// ``` /// # use rand::prelude::*; /// # use rand_distr::{Normal, StandardNormal}; - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// let z = StandardNormal.sample(&mut rng); /// let x1 = Normal::new(0.0, 1.0).unwrap().from_zscore(z); /// let x2 = Normal::new(2.0, -3.0).unwrap().from_zscore(z); @@ -266,7 +266,7 @@ where /// /// // mean 2, standard deviation 3 /// let log_normal = LogNormal::new(2.0, 3.0).unwrap(); -/// let v = log_normal.sample(&mut rand::thread_rng()); +/// let v = log_normal.sample(&mut rand::rng()); /// println!("{} is from an ln N(2, 9) distribution", v) /// ``` #[derive(Clone, Copy, Debug, PartialEq)] @@ -341,7 +341,7 @@ where /// ``` /// # use rand::prelude::*; /// # use rand_distr::{LogNormal, StandardNormal}; - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// let z = StandardNormal.sample(&mut rng); /// let x1 = LogNormal::from_mean_cv(3.0, 1.0).unwrap().from_zscore(z); /// let x2 = LogNormal::from_mean_cv(2.0, 4.0).unwrap().from_zscore(z); diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index 1c60b87d1bd..cd9c1781ce4 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -45,7 +45,7 @@ impl std::error::Error for Error {} /// use rand_distr::{NormalInverseGaussian, Distribution}; /// /// let norm_inv_gauss = NormalInverseGaussian::new(2.0, 1.0).unwrap(); -/// let v = norm_inv_gauss.sample(&mut rand::thread_rng()); +/// let v = norm_inv_gauss.sample(&mut rand::rng()); /// println!("{} is from a normal-inverse Gaussian(2, 1) distribution", v); /// ``` #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index 250891d37f1..7334ccd5f15 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -32,7 +32,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Pareto; /// -/// let val: f64 = thread_rng().sample(Pareto::new(1., 2.).unwrap()); +/// let val: f64 = rand::rng().sample(Pareto::new(1., 2.).unwrap()); /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/pert.rs b/rand_distr/src/pert.rs index b14d0e1ff59..5c247a3d1e8 100644 --- a/rand_distr/src/pert.rs +++ b/rand_distr/src/pert.rs @@ -32,7 +32,7 @@ use rand::Rng; /// use rand_distr::{Pert, Distribution}; /// /// let d = Pert::new(0., 5.).with_mode(2.5).unwrap(); -/// let v = d.sample(&mut rand::thread_rng()); +/// let v = d.sample(&mut rand::rng()); /// println!("{} is from a PERT distribution", v); /// ``` /// diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 759f39cde79..f06c29aef41 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -36,7 +36,7 @@ use rand::Rng; /// use rand_distr::{Poisson, Distribution}; /// /// let poi = Poisson::new(2.0).unwrap(); -/// let v: f64 = poi.sample(&mut rand::thread_rng()); +/// let v: f64 = poi.sample(&mut rand::rng()); /// println!("{} is from a Poisson(2) distribution", v); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/skew_normal.rs b/rand_distr/src/skew_normal.rs index 6cba670b17c..1be2311a6b5 100644 --- a/rand_distr/src/skew_normal.rs +++ b/rand_distr/src/skew_normal.rs @@ -45,7 +45,7 @@ use rand::Rng; /// /// // location 2, scale 3, shape 1 /// let skew_normal = SkewNormal::new(2.0, 3.0, 1.0).unwrap(); -/// let v = skew_normal.sample(&mut rand::thread_rng()); +/// let v = skew_normal.sample(&mut rand::rng()); /// println!("{} is from a SN(2, 3, 1) distribution", v) /// ``` /// diff --git a/rand_distr/src/student_t.rs b/rand_distr/src/student_t.rs index a84a0961604..b0d7d078ae2 100644 --- a/rand_distr/src/student_t.rs +++ b/rand_distr/src/student_t.rs @@ -42,7 +42,7 @@ use serde::{Deserialize, Serialize}; /// use rand_distr::{StudentT, Distribution}; /// /// let t = StudentT::new(11.0).unwrap(); -/// let v = t.sample(&mut rand::thread_rng()); +/// let v = t.sample(&mut rand::rng()); /// println!("{} is from a t(11) distribution", v) /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index c1b151a576f..9342ba024a1 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -33,7 +33,7 @@ use rand::Rng; /// use rand_distr::{Triangular, Distribution}; /// /// let d = Triangular::new(0., 5., 2.5).unwrap(); -/// let v = d.sample(&mut rand::thread_rng()); +/// let v = d.sample(&mut rand::rng()); /// println!("{} is from a triangular distribution", v); /// ``` /// diff --git a/rand_distr/src/unit_ball.rs b/rand_distr/src/unit_ball.rs index 025c1557ad7..514fc30812a 100644 --- a/rand_distr/src/unit_ball.rs +++ b/rand_distr/src/unit_ball.rs @@ -32,7 +32,7 @@ use rand::Rng; /// ``` /// use rand_distr::{UnitBall, Distribution}; /// -/// let v: [f64; 3] = UnitBall.sample(&mut rand::thread_rng()); +/// let v: [f64; 3] = UnitBall.sample(&mut rand::rng()); /// println!("{:?} is from the unit ball.", v) /// ``` #[derive(Clone, Copy, Debug)] diff --git a/rand_distr/src/unit_circle.rs b/rand_distr/src/unit_circle.rs index 3caee8f629f..d25d829f5a5 100644 --- a/rand_distr/src/unit_circle.rs +++ b/rand_distr/src/unit_circle.rs @@ -30,7 +30,7 @@ use rand::Rng; /// ``` /// use rand_distr::{UnitCircle, Distribution}; /// -/// let v: [f64; 2] = UnitCircle.sample(&mut rand::thread_rng()); +/// let v: [f64; 2] = UnitCircle.sample(&mut rand::rng()); /// println!("{:?} is from the unit circle.", v) /// ``` /// diff --git a/rand_distr/src/unit_disc.rs b/rand_distr/src/unit_disc.rs index a472de2a339..c95fd1d6c83 100644 --- a/rand_distr/src/unit_disc.rs +++ b/rand_distr/src/unit_disc.rs @@ -31,7 +31,7 @@ use rand::Rng; /// ``` /// use rand_distr::{UnitDisc, Distribution}; /// -/// let v: [f64; 2] = UnitDisc.sample(&mut rand::thread_rng()); +/// let v: [f64; 2] = UnitDisc.sample(&mut rand::rng()); /// println!("{:?} is from the unit Disc.", v) /// ``` #[derive(Clone, Copy, Debug)] diff --git a/rand_distr/src/unit_sphere.rs b/rand_distr/src/unit_sphere.rs index 2edd0f83798..1d531924efb 100644 --- a/rand_distr/src/unit_sphere.rs +++ b/rand_distr/src/unit_sphere.rs @@ -32,7 +32,7 @@ use rand::Rng; /// ``` /// use rand_distr::{UnitSphere, Distribution}; /// -/// let v: [f64; 3] = UnitSphere.sample(&mut rand::thread_rng()); +/// let v: [f64; 3] = UnitSphere.sample(&mut rand::rng()); /// println!("{:?} is from the unit sphere surface.", v) /// ``` /// diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index 3d11c7270cf..cb01302284b 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -30,7 +30,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Weibull; /// -/// let val: f64 = thread_rng().sample(Weibull::new(1., 10.).unwrap()); +/// let val: f64 = rand::rng().sample(Weibull::new(1., 10.).unwrap()); /// println!("{}", val); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted_alias.rs index 537060f3888..676689f2ad7 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted_alias.rs @@ -47,7 +47,7 @@ use serde::{Deserialize, Serialize}; /// let choices = vec!['a', 'b', 'c']; /// let weights = vec![2, 1, 1]; /// let dist = WeightedAliasIndex::new(weights).unwrap(); -/// let mut rng = thread_rng(); +/// let mut rng = rand::rng(); /// for _ in 0..100 { /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c' /// println!("{}", choices[dist.sample(&mut rng)]); diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index ef95ba41923..07d52ef19a3 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -66,7 +66,7 @@ use serde::{Deserialize, Serialize}; /// let mut dist = WeightedTreeIndex::new(&weights).unwrap(); /// dist.push(1).unwrap(); /// dist.update(1, 1).unwrap(); -/// let mut rng = thread_rng(); +/// let mut rng = rand::rng(); /// let mut samples = [0; 3]; /// for _ in 0..100 { /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c' diff --git a/rand_distr/src/zeta.rs b/rand_distr/src/zeta.rs index 922458436f5..3c2f55546e5 100644 --- a/rand_distr/src/zeta.rs +++ b/rand_distr/src/zeta.rs @@ -36,7 +36,7 @@ use rand::{distr::OpenClosed01, Rng}; /// use rand::prelude::*; /// use rand_distr::Zeta; /// -/// let val: f64 = thread_rng().sample(Zeta::new(1.5).unwrap()); +/// let val: f64 = rand::rng().sample(Zeta::new(1.5).unwrap()); /// println!("{}", val); /// ``` /// diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 70bb891aa7f..0a56fdf8182 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -35,7 +35,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Zipf; /// -/// let val: f64 = thread_rng().sample(Zipf::new(10, 1.5).unwrap()); +/// let val: f64 = rand::rng().sample(Zipf::new(10, 1.5).unwrap()); /// println!("{}", val); /// ``` /// diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index 7ef5dea22bc..3ea401debf2 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -47,7 +47,7 @@ //! let rng = Pcg64Mcg::from_os_rng(); //! # let _: Pcg64Mcg = rng; //! ``` -//! 3. **From a master generator.** This could be [`rand::thread_rng`] +//! 3. **From a master generator.** This could be [`rand::rng`] //! (effectively a fresh seed without the need for a syscall on each usage) //! or a deterministic generator such as [`rand_chacha::ChaCha8Rng`]. //! Beware that should a weak master generator be used, correlations may be @@ -77,7 +77,7 @@ //! [Random Values]: https://rust-random.github.io/book/guide-values.html //! [`RngCore`]: rand_core::RngCore //! [`SeedableRng`]: rand_core::SeedableRng -//! [`rand::thread_rng`]: https://docs.rs/rand/latest/rand/fn.thread_rng.html +//! [`rand::rng`]: https://docs.rs/rand/latest/rand/fn.rng.html //! [`rand::Rng`]: https://docs.rs/rand/latest/rand/trait.Rng.html //! [`rand_chacha::ChaCha8Rng`]: https://docs.rs/rand_chacha/latest/rand_chacha/struct.ChaCha8Rng.html diff --git a/src/distr/bernoulli.rs b/src/distr/bernoulli.rs index d71660b985f..84befcc8593 100644 --- a/src/distr/bernoulli.rs +++ b/src/distr/bernoulli.rs @@ -34,7 +34,7 @@ use serde::{Deserialize, Serialize}; /// use rand::distr::{Bernoulli, Distribution}; /// /// let d = Bernoulli::new(0.3).unwrap(); -/// let v = d.sample(&mut rand::thread_rng()); +/// let v = d.sample(&mut rand::rng()); /// println!("{} is from a Bernoulli distribution", v); /// ``` /// diff --git a/src/distr/distribution.rs b/src/distr/distribution.rs index 8a59e11e13c..702e439e0d4 100644 --- a/src/distr/distribution.rs +++ b/src/distr/distribution.rs @@ -48,10 +48,9 @@ pub trait Distribution { /// # Example /// /// ``` - /// use rand::thread_rng; /// use rand::distr::{Distribution, Alphanumeric, Uniform, Standard}; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// /// // Vec of 16 x f32: /// let v: Vec = Standard.sample_iter(&mut rng).take(16).collect(); @@ -88,10 +87,9 @@ pub trait Distribution { /// # Example /// /// ``` - /// use rand::thread_rng; /// use rand::distr::{Distribution, Uniform}; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// /// let die = Uniform::new_inclusive(1, 6).unwrap(); /// let even_number = die.map(|num| num % 2 == 0); diff --git a/src/distr/float.rs b/src/distr/float.rs index a8cbc96bd6f..11d02f76f0e 100644 --- a/src/distr/float.rs +++ b/src/distr/float.rs @@ -32,10 +32,10 @@ use serde::{Deserialize, Serialize}; /// /// # Example /// ``` -/// use rand::{thread_rng, Rng}; +/// use rand::Rng; /// use rand::distr::OpenClosed01; /// -/// let val: f32 = thread_rng().sample(OpenClosed01); +/// let val: f32 = rand::rng().sample(OpenClosed01); /// println!("f32 from (0, 1): {}", val); /// ``` /// @@ -59,10 +59,10 @@ pub struct OpenClosed01; /// /// # Example /// ``` -/// use rand::{thread_rng, Rng}; +/// use rand::Rng; /// use rand::distr::Open01; /// -/// let val: f32 = thread_rng().sample(Open01); +/// let val: f32 = rand::rng().sample(Open01); /// println!("f32 from (0, 1): {}", val); /// ``` /// diff --git a/src/distr/other.rs b/src/distr/other.rs index b2e91e53256..f350731477b 100644 --- a/src/distr/other.rs +++ b/src/distr/other.rs @@ -34,10 +34,10 @@ use serde::{Deserialize, Serialize}; /// # Example /// /// ``` -/// use rand::{Rng, thread_rng}; +/// use rand::Rng; /// use rand::distr::Alphanumeric; /// -/// let mut rng = thread_rng(); +/// let mut rng = rand::rng(); /// let chars: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect(); /// println!("Random chars: {}", chars); /// ``` @@ -46,7 +46,7 @@ use serde::{Deserialize, Serialize}; /// a random `String`, and offers more efficient allocation: /// ``` /// use rand::distr::{Alphanumeric, DistString}; -/// let string = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); +/// let string = Alphanumeric.sample_string(&mut rand::rng(), 16); /// println!("Random string: {}", string); /// ``` /// @@ -163,7 +163,7 @@ impl Distribution for Standard { /// #![feature(portable_simd)] /// use std::simd::prelude::*; /// use rand::prelude::*; -/// let mut rng = thread_rng(); +/// let mut rng = rand::rng(); /// /// let x = u16x8::splat(rng.random::() as u16); /// let mask = u16x8::splat(1) << u16x8::from([0, 1, 2, 3, 4, 5, 6, 7]); diff --git a/src/distr/slice.rs b/src/distr/slice.rs index d201caa2463..4e85610a6f7 100644 --- a/src/distr/slice.rs +++ b/src/distr/slice.rs @@ -38,7 +38,7 @@ use alloc::string::String; /// /// let vowels = ['a', 'e', 'i', 'o', 'u']; /// let vowels_dist = Slice::new(&vowels).unwrap(); -/// let rng = rand::thread_rng(); +/// let rng = rand::rng(); /// /// // build a string of 10 vowels /// let vowel_string: String = rng @@ -58,7 +58,7 @@ use alloc::string::String; /// use rand::seq::IndexedRandom; /// /// let vowels = ['a', 'e', 'i', 'o', 'u']; -/// let mut rng = rand::thread_rng(); +/// let mut rng = rand::rng(); /// /// println!("{}", vowels.choose(&mut rng).unwrap()) /// ``` diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 86a08fdc59f..210b4a5e22f 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -26,10 +26,10 @@ //! # Example usage //! //! ``` -//! use rand::{Rng, thread_rng}; +//! use rand::Rng; //! use rand::distr::Uniform; //! -//! let mut rng = thread_rng(); +//! let mut rng = rand::rng(); //! let side = Uniform::new(-10.0, 10.0).unwrap(); //! //! // sample between 1 and 10 points @@ -94,7 +94,7 @@ //! //! let (low, high) = (MyF32(17.0f32), MyF32(22.0f32)); //! let uniform = Uniform::new(low, high).unwrap(); -//! let x = uniform.sample(&mut thread_rng()); +//! let x = uniform.sample(&mut rand::rng()); //! ``` //! //! [`SampleUniform`]: crate::distr::uniform::SampleUniform @@ -178,7 +178,7 @@ use serde::{Deserialize, Serialize}; /// use rand::distr::{Distribution, Uniform}; /// /// let between = Uniform::try_from(10..10000).unwrap(); -/// let mut rng = rand::thread_rng(); +/// let mut rng = rand::rng(); /// let mut sum = 0; /// for _ in 0..1000 { /// sum += between.sample(&mut rng); @@ -191,7 +191,7 @@ use serde::{Deserialize, Serialize}; /// ``` /// use rand::Rng; /// -/// let mut rng = rand::thread_rng(); +/// let mut rng = rand::rng(); /// println!("{}", rng.gen_range(0..10)); /// ``` /// @@ -315,10 +315,10 @@ pub trait UniformSampler: Sized { /// Note that to use this method in a generic context, the type needs to be /// retrieved via `SampleUniform::Sampler` as follows: /// ``` - /// use rand::{thread_rng, distr::uniform::{SampleUniform, UniformSampler}}; + /// use rand::distr::uniform::{SampleUniform, UniformSampler}; /// # #[allow(unused)] /// fn sample_from_range(lb: T, ub: T) -> T { - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// ::Sampler::sample_single(lb, ub, &mut rng).unwrap() /// } /// ``` diff --git a/src/distr/weighted_index.rs b/src/distr/weighted_index.rs index 0d8eb7d170b..3aed484df19 100644 --- a/src/distr/weighted_index.rs +++ b/src/distr/weighted_index.rs @@ -64,7 +64,7 @@ use serde::{Deserialize, Serialize}; /// let choices = ['a', 'b', 'c']; /// let weights = [2, 1, 1]; /// let dist = WeightedIndex::new(&weights).unwrap(); -/// let mut rng = thread_rng(); +/// let mut rng = rand::rng(); /// for _ in 0..100 { /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c' /// println!("{}", choices[dist.sample(&mut rng)]); diff --git a/src/lib.rs b/src/lib.rs index 958c15d481c..833fe0c0e46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! use rand::prelude::*; //! //! // Get an RNG: -//! let mut rng = rand::thread_rng(); +//! let mut rng = rand::rng(); //! //! // Try printing a random unicode code point (probably a bad idea)! //! println!("char: '{}'", rng.random::()); @@ -104,7 +104,18 @@ pub mod seq; // Public exports #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] -pub use crate::rngs::thread::thread_rng; +pub use crate::rngs::thread::rng; + +/// Access the thread-local generator +/// +/// Use [`rand::rng()`](rng()) instead. +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[deprecated(since = "0.9.0", note = "renamed to `rng`")] +#[inline] +pub fn thread_rng() -> crate::rngs::ThreadRng { + rng() +} + pub use rng::{Fill, Rng}; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] @@ -112,7 +123,7 @@ use crate::distr::{Distribution, Standard}; /// Generates a random value using the thread-local random number generator. /// -/// This function is simply a shortcut for `thread_rng().gen()`: +/// This function is simply a shortcut for `rand::rng().gen()`: /// /// - See [`ThreadRng`] for documentation of the generator and security /// - See [`Standard`] for documentation of supported types and distributions @@ -143,9 +154,9 @@ use crate::distr::{Distribution, Standard}; /// *x = rand::random() /// } /// -/// // can be made faster by caching thread_rng +/// // can be made faster by caching rand::rng /// -/// let mut rng = rand::thread_rng(); +/// let mut rng = rand::rng(); /// /// for x in v.iter_mut() { /// *x = rng.random(); @@ -160,7 +171,7 @@ pub fn random() -> T where Standard: Distribution, { - thread_rng().random() + rng().random() } #[cfg(test)] diff --git a/src/prelude.rs b/src/prelude.rs index 803c641ad90..d41ad6b494b 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -14,7 +14,7 @@ //! //! ``` //! use rand::prelude::*; -//! # let mut r = StdRng::from_rng(&mut thread_rng()); +//! # let mut r = StdRng::from_rng(&mut rand::rng()); //! # let _: f32 = r.random(); //! ``` @@ -32,7 +32,4 @@ pub use crate::rngs::ThreadRng; #[doc(no_inline)] pub use crate::seq::{IndexedMutRandom, IndexedRandom, IteratorRandom, SliceRandom}; #[doc(no_inline)] -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] -pub use crate::{random, thread_rng}; -#[doc(no_inline)] pub use crate::{CryptoRng, Rng, RngCore, SeedableRng}; diff --git a/src/rng.rs b/src/rng.rs index 45361a00ab4..dadbc228f1f 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -37,7 +37,7 @@ use zerocopy::IntoBytes; /// /// An alternative pattern is possible: `fn foo(rng: R)`. This has some /// trade-offs. It allows the argument to be consumed directly without a `&mut` -/// (which is how `from_rng(thread_rng())` works); also it still works directly +/// (which is how `from_rng(rand::rng())` works); also it still works directly /// on references (including type-erased references). Unfortunately within the /// function `foo` it is not known whether `rng` is a reference type or not, /// hence many uses of `rng` require an extra reference, either explicitly @@ -47,14 +47,13 @@ use zerocopy::IntoBytes; /// Example: /// /// ``` -/// # use rand::thread_rng; /// use rand::Rng; /// /// fn foo(rng: &mut R) -> f32 { /// rng.random() /// } /// -/// # let v = foo(&mut thread_rng()); +/// # let v = foo(&mut rand::rng()); /// ``` pub trait Rng: RngCore { /// Return a random value via the [`Standard`] distribution. @@ -62,9 +61,9 @@ pub trait Rng: RngCore { /// # Example /// /// ``` - /// use rand::{thread_rng, Rng}; + /// use rand::Rng; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// let x: u32 = rng.random(); /// println!("{}", x); /// println!("{:?}", rng.random::<(f64, bool)>()); @@ -81,9 +80,9 @@ pub trait Rng: RngCore { /// though note that generated values will differ. /// /// ``` - /// use rand::{thread_rng, Rng}; + /// use rand::Rng; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// let tuple: (u8, i32, char) = rng.random(); // arbitrary tuple support /// /// let arr1: [f32; 32] = rng.random(); // array construction @@ -131,10 +130,10 @@ pub trait Rng: RngCore { /// ### Example /// /// ``` - /// use rand::{thread_rng, Rng}; + /// use rand::Rng; /// use rand::distr::Uniform; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// let x = rng.sample(Uniform::new(10u32, 15).unwrap()); /// // Type annotation requires two types, the type and distribution; the /// // distribution can be inferred. @@ -152,10 +151,10 @@ pub trait Rng: RngCore { /// # Example /// /// ``` - /// use rand::{thread_rng, Rng}; + /// use rand::Rng; /// use rand::distr::{Alphanumeric, Uniform, Standard}; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// /// // Vec of 16 x f32: /// let v: Vec = (&mut rng).sample_iter(Standard).take(16).collect(); @@ -197,10 +196,10 @@ pub trait Rng: RngCore { /// # Example /// /// ``` - /// use rand::{thread_rng, Rng}; + /// use rand::Rng; /// /// let mut arr = [0i8; 20]; - /// thread_rng().fill(&mut arr[..]); + /// rand::rng().fill(&mut arr[..]); /// ``` /// /// [`fill_bytes`]: RngCore::fill_bytes @@ -225,9 +224,9 @@ pub trait Rng: RngCore { /// # Example /// /// ``` - /// use rand::{thread_rng, Rng}; + /// use rand::Rng; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// /// // Exclusive range /// let n: u32 = rng.gen_range(..10); @@ -259,9 +258,9 @@ pub trait Rng: RngCore { /// # Example /// /// ``` - /// use rand::{thread_rng, Rng}; + /// use rand::Rng; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// println!("{}", rng.gen_bool(1.0 / 3.0)); /// ``` /// @@ -295,9 +294,9 @@ pub trait Rng: RngCore { /// # Example /// /// ``` - /// use rand::{thread_rng, Rng}; + /// use rand::Rng; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// println!("{}", rng.gen_ratio(2, 3)); /// ``` /// diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index 1aa65149a14..bfb5bf6e960 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -17,7 +17,7 @@ //! //! - [`OsRng`] is a stateless interface over the operating system's random number //! source. This is typically secure with some form of periodic re-seeding. -//! - [`ThreadRng`], provided by the [`thread_rng`] function, is a handle to a +//! - [`ThreadRng`], provided by [`crate::rng()`], is a handle to a //! thread-local generator with periodic seeding from [`OsRng`]. Because this //! is local, it is typically much faster than [`OsRng`]. It should be //! secure, but see documentation on [`ThreadRng`]. @@ -67,7 +67,6 @@ //! [`RngCore`]: crate::RngCore //! [`CryptoRng`]: crate::CryptoRng //! [`SeedableRng`]: crate::SeedableRng -//! [`thread_rng`]: crate::thread_rng //! [`rdrand`]: https://crates.io/crates/rdrand //! [`rand_jitter`]: https://crates.io/crates/rand_jitter //! [`rand_chacha`]: https://crates.io/crates/rand_chacha diff --git a/src/rngs/small.rs b/src/rngs/small.rs index e2b1d211253..1f9e7f5b8c9 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -114,17 +114,17 @@ impl RngCore for SmallRng { } impl SmallRng { - /// Construct an instance seeded from the thread-local RNG + /// Construct an instance seeded from `rand::rng` /// /// # Panics /// - /// This method panics only if [`thread_rng`](crate::thread_rng) fails to + /// This method panics only if [`crate::rng()`] fails to /// initialize. #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] #[inline(always)] pub fn from_thread_rng() -> Self { let mut seed = ::Seed::default(); - crate::thread_rng().fill_bytes(seed.as_mut()); + crate::rng().fill_bytes(seed.as_mut()); SmallRng(Rng::from_seed(seed)) } } diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 45b353088e3..bc7664934ee 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -41,7 +41,8 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// A reference to the thread-local generator /// /// This type is a reference to a lazily-initialized thread-local generator. -/// An instance can be obtained via [`thread_rng`] or via `ThreadRng::default()`. +/// An instance can be obtained via [`rand::rng()`][crate::rng())] or via +/// `ThreadRng::default()`. /// The handle cannot be passed between threads (is not `Send` or `Sync`). /// /// `ThreadRng` uses the same CSPRNG as [`StdRng`], ChaCha12. As with @@ -58,7 +59,7 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// let pid = unsafe { libc::fork() }; /// if pid == 0 { /// // Reseed ThreadRng in child processes: -/// rand::thread_rng().reseed(); +/// rand::rng().reseed(); /// } /// } /// ``` @@ -102,11 +103,11 @@ impl fmt::Debug for ThreadRng { } thread_local!( - // We require Rc<..> to avoid premature freeing when thread_rng is used + // We require Rc<..> to avoid premature freeing when ThreadRng is used // within thread-local destructors. See #968. static THREAD_RNG_KEY: Rc>> = { let r = Core::try_from_os_rng().unwrap_or_else(|err| - panic!("could not initialize thread_rng: {}", err)); + panic!("could not initialize ThreadRng: {}", err)); let rng = ReseedingRng::new(r, THREAD_RNG_RESEED_THRESHOLD, OsRng); @@ -114,31 +115,38 @@ thread_local!( } ); -/// Access the thread-local generator +/// Access a local, pre-initialized generator /// -/// Returns a reference to the local [`ThreadRng`], initializing the generator -/// on the first call on each thread. +/// This is a reasonably fast unpredictable thread-local instance of [`ThreadRng`]. +/// +/// See also [`crate::rngs`] for alternatives. +/// +/// # Example /// -/// Example usage: /// ``` -/// use rand::Rng; +/// use rand::prelude::*; /// /// # fn main() { -/// // rand::random() may be used instead of rand::thread_rng().gen(): -/// println!("A random boolean: {}", rand::random::()); /// -/// let mut rng = rand::thread_rng(); +/// let mut numbers = [1, 2, 3, 4, 5]; +/// numbers.shuffle(&mut rand::rng()); +/// println!("Numbers: {numbers:?}"); +/// +/// // Using a local binding avoids an initialization-check on each usage: +/// let mut rng = rand::rng(); +/// +/// println!("True or false: {}", rng.random::()); /// println!("A simulated die roll: {}", rng.gen_range(1..=6)); /// # } /// ``` -pub fn thread_rng() -> ThreadRng { +pub fn rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng } } impl Default for ThreadRng { fn default() -> ThreadRng { - thread_rng() + rng() } } @@ -175,7 +183,7 @@ mod test { #[test] fn test_thread_rng() { use crate::Rng; - let mut r = crate::thread_rng(); + let mut r = crate::rng(); r.random::(); assert_eq!(r.gen_range(0..1), 0); } @@ -184,9 +192,6 @@ mod test { fn test_debug_output() { // We don't care about the exact output here, but it must not include // private CSPRNG state or the cache stored by BlockRng! - assert_eq!( - std::format!("{:?}", crate::thread_rng()), - "ThreadRng { .. }" - ); + assert_eq!(std::format!("{:?}", crate::rng()), "ThreadRng { .. }"); } } diff --git a/src/seq/iterator.rs b/src/seq/iterator.rs index 148093157da..af9db6917a6 100644 --- a/src/seq/iterator.rs +++ b/src/seq/iterator.rs @@ -24,10 +24,8 @@ use alloc::vec::Vec; /// ``` /// use rand::seq::IteratorRandom; /// -/// let mut rng = rand::thread_rng(); -/// /// let faces = "😀😎😐😕😠😢"; -/// println!("I am {}!", faces.chars().choose(&mut rng).unwrap()); +/// println!("I am {}!", faces.chars().choose(&mut rand::rng()).unwrap()); /// ``` /// Example output (non-deterministic): /// ```none diff --git a/src/seq/slice.rs b/src/seq/slice.rs index 930e5450943..2b423e46b87 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -42,11 +42,10 @@ pub trait IndexedRandom: Index { /// # Example /// /// ``` - /// use rand::thread_rng; /// use rand::seq::IndexedRandom; /// /// let choices = [1, 2, 4, 8, 16, 32]; - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// println!("{:?}", choices.choose(&mut rng)); /// assert_eq!(choices[..0].choose(&mut rng), None); /// ``` @@ -75,7 +74,7 @@ pub trait IndexedRandom: Index { /// ``` /// use rand::seq::IndexedRandom; /// - /// let mut rng = &mut rand::thread_rng(); + /// let mut rng = &mut rand::rng(); /// let sample = "Hello, audience!".as_bytes(); /// /// // collect the results into a vector: @@ -112,7 +111,7 @@ pub trait IndexedRandom: Index { /// ``` /// use rand::seq::IndexedRandom; /// - /// let mut rng = &mut rand::thread_rng(); + /// let mut rng = &mut rand::rng(); /// let sample = "Hello, audience!".as_bytes(); /// /// let a: [u8; 3] = sample.choose_multiple_array(&mut rng).unwrap(); @@ -147,7 +146,7 @@ pub trait IndexedRandom: Index { /// use rand::prelude::*; /// /// let choices = [('a', 2), ('b', 1), ('c', 1), ('d', 0)]; - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// // 50% chance to print 'a', 25% chance to print 'b', 25% chance to print 'c', /// // and 'd' will never be printed /// println!("{:?}", choices.choose_weighted(&mut rng, |item| item.1).unwrap().0); @@ -201,7 +200,7 @@ pub trait IndexedRandom: Index { /// use rand::prelude::*; /// /// let choices = [('a', 2), ('b', 1), ('c', 1)]; - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// // First Draw * Second Draw = total odds /// // ----------------------- /// // (50% * 50%) + (25% * 67%) = 41.7% chance that the output is `['a', 'b']` in some order. @@ -308,7 +307,7 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { /// ``` /// use rand::seq::SliceRandom; /// -/// let mut rng = rand::thread_rng(); +/// let mut rng = rand::rng(); /// let mut bytes = "Hello, random!".to_string().into_bytes(); /// bytes.shuffle(&mut rng); /// let str = String::from_utf8(bytes).unwrap(); @@ -328,9 +327,8 @@ pub trait SliceRandom: IndexedMutRandom { /// /// ``` /// use rand::seq::SliceRandom; - /// use rand::thread_rng; /// - /// let mut rng = thread_rng(); + /// let mut rng = rand::rng(); /// let mut y = [1, 2, 3, 4, 5]; /// println!("Unshuffled: {:?}", y); /// y.shuffle(&mut rng); From 695fc9a5f5cd373c79a63ef715078f1a0c0aa043 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Wed, 16 Oct 2024 02:54:28 +0300 Subject: [PATCH 415/443] Fix new Clippy lints (#1511) --- rand_core/src/block.rs | 4 ++-- rand_distr/src/utils.rs | 5 +---- src/distr/distribution.rs | 2 +- src/distr/slice.rs | 2 +- src/distr/uniform.rs | 2 +- src/distr/weighted_index.rs | 6 +++--- src/seq/index.rs | 4 ++-- 7 files changed, 11 insertions(+), 14 deletions(-) diff --git a/rand_core/src/block.rs b/rand_core/src/block.rs index c0243d4c132..aa2252e6da2 100644 --- a/rand_core/src/block.rs +++ b/rand_core/src/block.rs @@ -120,10 +120,10 @@ pub trait CryptoBlockRng: BlockRngCore {} #[cfg_attr( feature = "serde", serde( - bound = "for<'x> R: Serialize + Deserialize<'x> + Sized, for<'x> R::Results: Serialize + Deserialize<'x>" + bound = "for<'x> R: Serialize + Deserialize<'x>, for<'x> R::Results: Serialize + Deserialize<'x>" ) )] -pub struct BlockRng { +pub struct BlockRng { results: R::Results, index: usize, /// The *core* part of the RNG, implementing the `generate` function. diff --git a/rand_distr/src/utils.rs b/rand_distr/src/utils.rs index 5879a152670..f0cf2a1005a 100644 --- a/rand_distr/src/utils.rs +++ b/rand_distr/src/utils.rs @@ -67,10 +67,7 @@ pub(crate) fn log_gamma(x: F) -> F { /// * `pdf`: the probability density function /// * `zero_case`: manual sampling from the tail when we chose the /// bottom box (i.e. i == 0) - -// the perf improvement (25-50%) is definitely worth the extra code -// size from force-inlining. -#[inline(always)] +#[inline(always)] // Forced inlining improves the perf by 25-50% pub(crate) fn ziggurat( rng: &mut R, symmetric: bool, diff --git a/src/distr/distribution.rs b/src/distr/distribution.rs index 702e439e0d4..cab4ce626fd 100644 --- a/src/distr/distribution.rs +++ b/src/distr/distribution.rs @@ -110,7 +110,7 @@ pub trait Distribution { } } -impl<'a, T, D: Distribution + ?Sized> Distribution for &'a D { +impl + ?Sized> Distribution for &D { fn sample(&self, rng: &mut R) -> T { (*self).sample(rng) } diff --git a/src/distr/slice.rs b/src/distr/slice.rs index 4e85610a6f7..3eee65a92ce 100644 --- a/src/distr/slice.rs +++ b/src/distr/slice.rs @@ -127,7 +127,7 @@ impl std::error::Error for EmptySlice {} /// Note: the `String` is potentially left with excess capacity; optionally the /// user may call `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -impl<'a> super::DistString for Slice<'a, char> { +impl super::DistString for Slice<'_, char> { fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { // Get the max char length to minimize extra space. // Limit this check to avoid searching for long slice. diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 210b4a5e22f..2368e04f4d9 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -393,7 +393,7 @@ where self } } -impl<'a, Borrowed> SampleBorrow for &'a Borrowed +impl SampleBorrow for &Borrowed where Borrowed: SampleUniform, { diff --git a/src/distr/weighted_index.rs b/src/distr/weighted_index.rs index 3aed484df19..fef5728e41a 100644 --- a/src/distr/weighted_index.rs +++ b/src/distr/weighted_index.rs @@ -256,7 +256,7 @@ pub struct WeightedIndexIter<'a, X: SampleUniform + PartialOrd> { index: usize, } -impl<'a, X> Debug for WeightedIndexIter<'a, X> +impl Debug for WeightedIndexIter<'_, X> where X: SampleUniform + PartialOrd + Debug, X::Sampler: Debug, @@ -269,7 +269,7 @@ where } } -impl<'a, X> Clone for WeightedIndexIter<'a, X> +impl Clone for WeightedIndexIter<'_, X> where X: SampleUniform + PartialOrd, { @@ -281,7 +281,7 @@ where } } -impl<'a, X> Iterator for WeightedIndexIter<'a, X> +impl Iterator for WeightedIndexIter<'_, X> where X: for<'b> core::ops::SubAssign<&'b X> + SampleUniform + PartialOrd + Clone, { diff --git a/src/seq/index.rs b/src/seq/index.rs index e66b5039883..b22d6dc4c9c 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -153,7 +153,7 @@ pub enum IndexVecIter<'a> { U64(slice::Iter<'a, u64>), } -impl<'a> Iterator for IndexVecIter<'a> { +impl Iterator for IndexVecIter<'_> { type Item = usize; #[inline] @@ -176,7 +176,7 @@ impl<'a> Iterator for IndexVecIter<'a> { } } -impl<'a> ExactSizeIterator for IndexVecIter<'a> {} +impl ExactSizeIterator for IndexVecIter<'_> {} /// Return type of `IndexVec::into_iter`. #[derive(Clone, Debug)] From 9d57b87e2700d0442b92578f3ed3137359704561 Mon Sep 17 00:00:00 2001 From: Benjamin Lieser Date: Wed, 16 Oct 2024 12:11:24 +0200 Subject: [PATCH 416/443] Weibull doc: add PDF and warning against small k (#1509) --- rand_distr/src/weibull.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index cb01302284b..1a9faf46c22 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -19,6 +19,10 @@ use rand::Rng; /// scale parameter `λ` (`lambda`) and shape parameter `k`. It is used /// to model reliability data, life data, and accelerated life testing data. /// +/// # Density function +/// +/// `f(x; λ, k) = (k / λ) * (x / λ)^(k - 1) * exp(-(x / λ)^k)` for `x >= 0`. +/// /// # Plot /// /// The following plot shows the Weibull distribution with various values of `λ` and `k`. @@ -33,6 +37,11 @@ use rand::Rng; /// let val: f64 = rand::rng().sample(Weibull::new(1., 10.).unwrap()); /// println!("{}", val); /// ``` +/// +/// # Numerics +/// +/// For small `k` like `< 0.005`, even with `f64` a significant number of samples will be so small that they underflow to `0.0` +/// or so big they overflow to `inf`. This is a limitation of the floating point representation and not specific to this implementation. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Weibull From 8225d948b1a67e003fed244d3fd55ba94601be49 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 16 Oct 2024 14:45:36 +0100 Subject: [PATCH 417/443] =?UTF-8?q?Rng=20renames:=20gen=5F=20=E2=86=92=20r?= =?UTF-8?q?andom=5F=20(#1505)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + benches/benches/array.rs | 2 +- benches/benches/bool.rs | 14 +- benches/benches/generators.rs | 14 +- rand_distr/src/weighted_tree.rs | 2 +- src/distr/mod.rs | 4 +- src/distr/uniform.rs | 14 +- src/distr/uniform_float.rs | 2 +- src/distr/uniform_other.rs | 2 +- src/rng.rs | 259 +++++++++++++++++--------------- src/rngs/thread.rs | 4 +- src/seq/coin_flipper.rs | 10 +- src/seq/increasing_uniform.rs | 4 +- src/seq/index.rs | 6 +- src/seq/iterator.rs | 14 +- src/seq/mod.rs | 2 +- src/seq/slice.rs | 8 +- 17 files changed, 194 insertions(+), 168 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a34b76964..179c889a73b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `Rng::gen_iter` to `random_iter` (#1500) - Rename `rand::thread_rng()` to `rand::rng()`, and remove from the prelude (#1506) - Remove `rand::random()` from the prelude (#1506) +- Rename `Rng::gen_range` to `random_range`, `gen_bool` to `random_bool`, `gen_ratio` to `random_ratio` (#1505) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/benches/benches/array.rs b/benches/benches/array.rs index bf328bc7a9b..a21e902cb95 100644 --- a/benches/benches/array.rs +++ b/benches/benches/array.rs @@ -21,7 +21,7 @@ criterion_group!( criterion_main!(benches); pub fn bench(c: &mut Criterion) { - let mut g = c.benchmark_group("gen_1kb"); + let mut g = c.benchmark_group("random_1kb"); g.throughput(criterion::Throughput::Bytes(1024)); g.bench_function("u16_iter_repeat", |b| { diff --git a/benches/benches/bool.rs b/benches/benches/bool.rs index 0584c1ddfc6..989a938d9f8 100644 --- a/benches/benches/bool.rs +++ b/benches/benches/bool.rs @@ -21,7 +21,7 @@ criterion_group!( criterion_main!(benches); pub fn bench(c: &mut Criterion) { - let mut g = c.benchmark_group("gen_bool"); + let mut g = c.benchmark_group("random_bool"); g.sample_size(1000); g.warm_up_time(core::time::Duration::from_millis(500)); g.measurement_time(core::time::Duration::from_millis(1000)); @@ -33,25 +33,25 @@ pub fn bench(c: &mut Criterion) { g.bench_function("const", |b| { let mut rng = Pcg32::from_rng(&mut rand::rng()); - b.iter(|| rng.gen_bool(0.18)) + b.iter(|| rng.random_bool(0.18)) }); g.bench_function("var", |b| { let mut rng = Pcg32::from_rng(&mut rand::rng()); let p = rng.random(); - b.iter(|| rng.gen_bool(p)) + b.iter(|| rng.random_bool(p)) }); g.bench_function("ratio_const", |b| { let mut rng = Pcg32::from_rng(&mut rand::rng()); - b.iter(|| rng.gen_ratio(2, 3)) + b.iter(|| rng.random_ratio(2, 3)) }); g.bench_function("ratio_var", |b| { let mut rng = Pcg32::from_rng(&mut rand::rng()); - let d = rng.gen_range(1..=100); - let n = rng.gen_range(0..=d); - b.iter(|| rng.gen_ratio(n, d)); + let d = rng.random_range(1..=100); + let n = rng.random_range(0..=d); + b.iter(|| rng.random_ratio(n, d)); }); g.bench_function("bernoulli_const", |b| { diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index a3370c2357a..cdfab9b2f7e 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -19,12 +19,12 @@ use rand_pcg::{Pcg32, Pcg64, Pcg64Dxsm, Pcg64Mcg}; criterion_group!( name = benches; config = Criterion::default(); - targets = gen_bytes, gen_u32, gen_u64, init_gen, init_from_u64, init_from_seed, reseeding_bytes + targets = random_bytes, random_u32, random_u64, init_gen, init_from_u64, init_from_seed, reseeding_bytes ); criterion_main!(benches); -pub fn gen_bytes(c: &mut Criterion) { - let mut g = c.benchmark_group("gen_bytes"); +pub fn random_bytes(c: &mut Criterion) { + let mut g = c.benchmark_group("random_bytes"); g.warm_up_time(Duration::from_millis(500)); g.measurement_time(Duration::from_millis(1000)); g.throughput(criterion::Throughput::Bytes(1024)); @@ -55,8 +55,8 @@ pub fn gen_bytes(c: &mut Criterion) { g.finish() } -pub fn gen_u32(c: &mut Criterion) { - let mut g = c.benchmark_group("gen_u32"); +pub fn random_u32(c: &mut Criterion) { + let mut g = c.benchmark_group("random_u32"); g.sample_size(1000); g.warm_up_time(Duration::from_millis(500)); g.measurement_time(Duration::from_millis(1000)); @@ -84,8 +84,8 @@ pub fn gen_u32(c: &mut Criterion) { g.finish() } -pub fn gen_u64(c: &mut Criterion) { - let mut g = c.benchmark_group("gen_u64"); +pub fn random_u64(c: &mut Criterion) { + let mut g = c.benchmark_group("random_u64"); g.sample_size(1000); g.warm_up_time(Duration::from_millis(500)); g.measurement_time(Duration::from_millis(1000)); diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted_tree.rs index 07d52ef19a3..355373a1b5a 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted_tree.rs @@ -251,7 +251,7 @@ impl + Weight> if total_weight == W::ZERO { return Err(WeightError::InsufficientNonZero); } - let mut target_weight = rng.gen_range(W::ZERO..total_weight); + let mut target_weight = rng.random_range(W::ZERO..total_weight); let mut index = 0; loop { // Maybe descend into the left sub tree. diff --git a/src/distr/mod.rs b/src/distr/mod.rs index 716aa417e3a..a1b2802c940 100644 --- a/src/distr/mod.rs +++ b/src/distr/mod.rs @@ -76,7 +76,7 @@ //! # Non-uniform sampling //! //! Sampling a simple true/false outcome with a given probability has a name: -//! the [`Bernoulli`] distribution (this is used by [`Rng::gen_bool`]). +//! the [`Bernoulli`] distribution (this is used by [`Rng::random_bool`]). //! //! For weighted sampling from a sequence of discrete values, use the //! [`WeightedIndex`] distribution. @@ -204,7 +204,7 @@ use crate::Rng; /// multiplicative method: `(rng.gen::<$uty>() >> N) as $ty * (ε/2)`. /// /// See also: [`Open01`] which samples from `(0, 1)`, [`OpenClosed01`] which -/// samples from `(0, 1]` and `Rng::gen_range(0..1)` which also samples from +/// samples from `(0, 1]` and `Rng::random_range(0..1)` which also samples from /// `[0, 1)`. Note that `Open01` uses transmute-based methods which yield 1 bit /// less precision but may perform faster on some architectures (on modern Intel /// CPUs all methods have approximately equal performance). diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 2368e04f4d9..2ab4f56b826 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -11,7 +11,7 @@ //! //! [`Uniform`] is the standard distribution to sample uniformly from a range; //! e.g. `Uniform::new_inclusive(1, 6).unwrap()` can sample integers from 1 to 6, like a -//! standard die. [`Rng::gen_range`] supports any type supported by [`Uniform`]. +//! standard die. [`Rng::random_range`] supports any type supported by [`Uniform`]. //! //! This distribution is provided with support for several primitive types //! (all integer and floating-point types) as well as [`std::time::Duration`], @@ -33,7 +33,7 @@ //! let side = Uniform::new(-10.0, 10.0).unwrap(); //! //! // sample between 1 and 10 points -//! for _ in 0..rng.gen_range(1..=10) { +//! for _ in 0..rng.random_range(1..=10) { //! // sample a point from the square with sides -10 - 10 in two dimensions //! let (x, y) = (rng.sample(side), rng.sample(side)); //! println!("Point: {}, {}", x, y); @@ -154,7 +154,7 @@ use serde::{Deserialize, Serialize}; /// [`Uniform::new`] and [`Uniform::new_inclusive`] construct a uniform /// distribution sampling from the given range; these functions may do extra /// work up front to make sampling of multiple values faster. If only one sample -/// from the range is required, [`Rng::gen_range`] can be more efficient. +/// from the range is required, [`Rng::random_range`] can be more efficient. /// /// When sampling from a constant range, many calculations can happen at /// compile-time and all methods should be fast; for floating-point ranges and @@ -186,18 +186,18 @@ use serde::{Deserialize, Serialize}; /// println!("{}", sum); /// ``` /// -/// For a single sample, [`Rng::gen_range`] may be preferred: +/// For a single sample, [`Rng::random_range`] may be preferred: /// /// ``` /// use rand::Rng; /// /// let mut rng = rand::rng(); -/// println!("{}", rng.gen_range(0..10)); +/// println!("{}", rng.random_range(0..10)); /// ``` /// /// [`new`]: Uniform::new /// [`new_inclusive`]: Uniform::new_inclusive -/// [`Rng::gen_range`]: Rng::gen_range +/// [`Rng::random_range`]: Rng::random_range #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(bound(serialize = "X::Sampler: Serialize")))] @@ -406,7 +406,7 @@ where /// Range that supports generating a single sample efficiently. /// /// Any type implementing this trait can be used to specify the sampled range -/// for `Rng::gen_range`. +/// for `Rng::random_range`. pub trait SampleRange { /// Generate a sample from the given range. fn sample_single(self, rng: &mut R) -> Result; diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index 2fd29b2383d..82fd68bbc88 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -364,7 +364,7 @@ mod tests { #[should_panic] fn test_float_overflow_single() { let mut rng = crate::test::rng(252); - rng.gen_range(f64::MIN..f64::MAX); + rng.random_range(f64::MIN..f64::MAX); } #[test] diff --git a/src/distr/uniform_other.rs b/src/distr/uniform_other.rs index f530451aeb0..42a7ff78134 100644 --- a/src/distr/uniform_other.rs +++ b/src/distr/uniform_other.rs @@ -265,7 +265,7 @@ mod tests { let mut rng = crate::test::rng(891); let mut max = core::char::from_u32(0).unwrap(); for _ in 0..100 { - let c = rng.gen_range('A'..='Z'); + let c = rng.random_range('A'..='Z'); assert!(c.is_ascii_uppercase()); max = max.max(c); } diff --git a/src/rng.rs b/src/rng.rs index dadbc228f1f..a3657ed45f0 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -125,89 +125,6 @@ pub trait Rng: RngCore { Standard.sample_iter(self) } - /// Sample a new value, using the given distribution. - /// - /// ### Example - /// - /// ``` - /// use rand::Rng; - /// use rand::distr::Uniform; - /// - /// let mut rng = rand::rng(); - /// let x = rng.sample(Uniform::new(10u32, 15).unwrap()); - /// // Type annotation requires two types, the type and distribution; the - /// // distribution can be inferred. - /// let y = rng.sample::(Uniform::new(10, 15).unwrap()); - /// ``` - fn sample>(&mut self, distr: D) -> T { - distr.sample(self) - } - - /// Create an iterator that generates values using the given distribution. - /// - /// Note: this method consumes its arguments. Use - /// `(&mut rng).sample_iter(..)` to avoid consuming the RNG. - /// - /// # Example - /// - /// ``` - /// use rand::Rng; - /// use rand::distr::{Alphanumeric, Uniform, Standard}; - /// - /// let mut rng = rand::rng(); - /// - /// // Vec of 16 x f32: - /// let v: Vec = (&mut rng).sample_iter(Standard).take(16).collect(); - /// - /// // String: - /// let s: String = (&mut rng).sample_iter(Alphanumeric) - /// .take(7) - /// .map(char::from) - /// .collect(); - /// - /// // Combined values - /// println!("{:?}", (&mut rng).sample_iter(Standard).take(5) - /// .collect::>()); - /// - /// // Dice-rolling: - /// let die_range = Uniform::new_inclusive(1, 6).unwrap(); - /// let mut roll_die = (&mut rng).sample_iter(die_range); - /// while roll_die.next().unwrap() != 6 { - /// println!("Not a 6; rolling again!"); - /// } - /// ``` - fn sample_iter(self, distr: D) -> distr::DistIter - where - D: Distribution, - Self: Sized, - { - distr.sample_iter(self) - } - - /// Fill any type implementing [`Fill`] with random data - /// - /// This method is implemented for types which may be safely reinterpreted - /// as an (aligned) `[u8]` slice then filled with random data. It is often - /// faster than using [`Rng::random`] but not value-equivalent. - /// - /// The distribution is expected to be uniform with portable results, but - /// this cannot be guaranteed for third-party implementations. - /// - /// # Example - /// - /// ``` - /// use rand::Rng; - /// - /// let mut arr = [0i8; 20]; - /// rand::rng().fill(&mut arr[..]); - /// ``` - /// - /// [`fill_bytes`]: RngCore::fill_bytes - #[track_caller] - fn fill(&mut self, dest: &mut T) { - dest.fill(self) - } - /// Generate a random value in the given range. /// /// This function is optimised for the case that only a single sample is @@ -229,19 +146,19 @@ pub trait Rng: RngCore { /// let mut rng = rand::rng(); /// /// // Exclusive range - /// let n: u32 = rng.gen_range(..10); + /// let n: u32 = rng.random_range(..10); /// println!("{}", n); - /// let m: f64 = rng.gen_range(-40.0..1.3e5); + /// let m: f64 = rng.random_range(-40.0..1.3e5); /// println!("{}", m); /// /// // Inclusive range - /// let n: u32 = rng.gen_range(..=10); + /// let n: u32 = rng.random_range(..=10); /// println!("{}", n); /// ``` /// /// [`Uniform`]: distr::uniform::Uniform #[track_caller] - fn gen_range(&mut self, range: R) -> T + fn random_range(&mut self, range: R) -> T where T: SampleUniform, R: SampleRange, @@ -261,7 +178,7 @@ pub trait Rng: RngCore { /// use rand::Rng; /// /// let mut rng = rand::rng(); - /// println!("{}", rng.gen_bool(1.0 / 3.0)); + /// println!("{}", rng.random_bool(1.0 / 3.0)); /// ``` /// /// # Panics @@ -271,7 +188,7 @@ pub trait Rng: RngCore { /// [`Bernoulli`]: distr::Bernoulli #[inline] #[track_caller] - fn gen_bool(&mut self, p: f64) -> bool { + fn random_bool(&mut self, p: f64) -> bool { match distr::Bernoulli::new(p) { Ok(d) => self.sample(d), Err(_) => panic!("p={:?} is outside range [0.0, 1.0]", p), @@ -279,7 +196,7 @@ pub trait Rng: RngCore { } /// Return a bool with a probability of `numerator/denominator` of being - /// true. I.e. `gen_ratio(2, 3)` has chance of 2 in 3, or about 67%, of + /// true. I.e. `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of /// returning true. If `numerator == denominator`, then the returned value /// is guaranteed to be `true`. If `numerator == 0`, then the returned /// value is guaranteed to be `false`. @@ -297,13 +214,13 @@ pub trait Rng: RngCore { /// use rand::Rng; /// /// let mut rng = rand::rng(); - /// println!("{}", rng.gen_ratio(2, 3)); + /// println!("{}", rng.random_ratio(2, 3)); /// ``` /// /// [`Bernoulli`]: distr::Bernoulli #[inline] #[track_caller] - fn gen_ratio(&mut self, numerator: u32, denominator: u32) -> bool { + fn random_ratio(&mut self, numerator: u32, denominator: u32) -> bool { match distr::Bernoulli::from_ratio(numerator, denominator) { Ok(d) => self.sample(d), Err(_) => panic!( @@ -313,6 +230,89 @@ pub trait Rng: RngCore { } } + /// Sample a new value, using the given distribution. + /// + /// ### Example + /// + /// ``` + /// use rand::Rng; + /// use rand::distr::Uniform; + /// + /// let mut rng = rand::rng(); + /// let x = rng.sample(Uniform::new(10u32, 15).unwrap()); + /// // Type annotation requires two types, the type and distribution; the + /// // distribution can be inferred. + /// let y = rng.sample::(Uniform::new(10, 15).unwrap()); + /// ``` + fn sample>(&mut self, distr: D) -> T { + distr.sample(self) + } + + /// Create an iterator that generates values using the given distribution. + /// + /// Note: this method consumes its arguments. Use + /// `(&mut rng).sample_iter(..)` to avoid consuming the RNG. + /// + /// # Example + /// + /// ``` + /// use rand::Rng; + /// use rand::distr::{Alphanumeric, Uniform, Standard}; + /// + /// let mut rng = rand::rng(); + /// + /// // Vec of 16 x f32: + /// let v: Vec = (&mut rng).sample_iter(Standard).take(16).collect(); + /// + /// // String: + /// let s: String = (&mut rng).sample_iter(Alphanumeric) + /// .take(7) + /// .map(char::from) + /// .collect(); + /// + /// // Combined values + /// println!("{:?}", (&mut rng).sample_iter(Standard).take(5) + /// .collect::>()); + /// + /// // Dice-rolling: + /// let die_range = Uniform::new_inclusive(1, 6).unwrap(); + /// let mut roll_die = (&mut rng).sample_iter(die_range); + /// while roll_die.next().unwrap() != 6 { + /// println!("Not a 6; rolling again!"); + /// } + /// ``` + fn sample_iter(self, distr: D) -> distr::DistIter + where + D: Distribution, + Self: Sized, + { + distr.sample_iter(self) + } + + /// Fill any type implementing [`Fill`] with random data + /// + /// This method is implemented for types which may be safely reinterpreted + /// as an (aligned) `[u8]` slice then filled with random data. It is often + /// faster than using [`Rng::random`] but not value-equivalent. + /// + /// The distribution is expected to be uniform with portable results, but + /// this cannot be guaranteed for third-party implementations. + /// + /// # Example + /// + /// ``` + /// use rand::Rng; + /// + /// let mut arr = [0i8; 20]; + /// rand::rng().fill(&mut arr[..]); + /// ``` + /// + /// [`fill_bytes`]: RngCore::fill_bytes + #[track_caller] + fn fill(&mut self, dest: &mut T) { + dest.fill(self) + } + /// Alias for [`Rng::random`]. #[inline] #[deprecated( @@ -325,6 +325,31 @@ pub trait Rng: RngCore { { self.random() } + + /// Alias for [`Rng::random_range`]. + #[inline] + #[deprecated(since = "0.9.0", note = "Renamed to `random_range`")] + fn gen_range(&mut self, range: R) -> T + where + T: SampleUniform, + R: SampleRange, + { + self.random_range(range) + } + + /// Alias for [`Rng::random_bool`]. + #[inline] + #[deprecated(since = "0.9.0", note = "Renamed to `random_bool`")] + fn gen_bool(&mut self, p: f64) -> bool { + self.random_bool(p) + } + + /// Alias for [`Rng::random_ratio`]. + #[inline] + #[deprecated(since = "0.9.0", note = "Renamed to `random_ratio`")] + fn gen_ratio(&mut self, numerator: u32, denominator: u32) -> bool { + self.random_ratio(numerator, denominator) + } } impl Rng for R {} @@ -480,64 +505,64 @@ mod test { } #[test] - fn test_gen_range_int() { + fn test_random_range_int() { let mut r = rng(101); for _ in 0..1000 { - let a = r.gen_range(-4711..17); + let a = r.random_range(-4711..17); assert!((-4711..17).contains(&a)); - let a: i8 = r.gen_range(-3..42); + let a: i8 = r.random_range(-3..42); assert!((-3..42).contains(&a)); - let a: u16 = r.gen_range(10..99); + let a: u16 = r.random_range(10..99); assert!((10..99).contains(&a)); - let a: i32 = r.gen_range(-100..2000); + let a: i32 = r.random_range(-100..2000); assert!((-100..2000).contains(&a)); - let a: u32 = r.gen_range(12..=24); + let a: u32 = r.random_range(12..=24); assert!((12..=24).contains(&a)); - assert_eq!(r.gen_range(..1u32), 0u32); - assert_eq!(r.gen_range(-12i64..-11), -12i64); - assert_eq!(r.gen_range(3_000_000..3_000_001), 3_000_000); + assert_eq!(r.random_range(..1u32), 0u32); + assert_eq!(r.random_range(-12i64..-11), -12i64); + assert_eq!(r.random_range(3_000_000..3_000_001), 3_000_000); } } #[test] - fn test_gen_range_float() { + fn test_random_range_float() { let mut r = rng(101); for _ in 0..1000 { - let a = r.gen_range(-4.5..1.7); + let a = r.random_range(-4.5..1.7); assert!((-4.5..1.7).contains(&a)); - let a = r.gen_range(-1.1..=-0.3); + let a = r.random_range(-1.1..=-0.3); assert!((-1.1..=-0.3).contains(&a)); - assert_eq!(r.gen_range(0.0f32..=0.0), 0.); - assert_eq!(r.gen_range(-11.0..=-11.0), -11.); - assert_eq!(r.gen_range(3_000_000.0..=3_000_000.0), 3_000_000.); + assert_eq!(r.random_range(0.0f32..=0.0), 0.); + assert_eq!(r.random_range(-11.0..=-11.0), -11.); + assert_eq!(r.random_range(3_000_000.0..=3_000_000.0), 3_000_000.); } } #[test] #[should_panic] #[allow(clippy::reversed_empty_ranges)] - fn test_gen_range_panic_int() { + fn test_random_range_panic_int() { let mut r = rng(102); - r.gen_range(5..-2); + r.random_range(5..-2); } #[test] #[should_panic] #[allow(clippy::reversed_empty_ranges)] - fn test_gen_range_panic_usize() { + fn test_random_range_panic_usize() { let mut r = rng(103); - r.gen_range(5..2); + r.random_range(5..2); } #[test] #[allow(clippy::bool_assert_comparison)] - fn test_gen_bool() { + fn test_random_bool() { let mut r = rng(105); for _ in 0..5 { - assert_eq!(r.gen_bool(0.0), false); - assert_eq!(r.gen_bool(1.0), true); + assert_eq!(r.random_bool(0.0), false); + assert_eq!(r.random_bool(1.0), true); } } @@ -558,7 +583,7 @@ mod test { let mut r = &mut rng as &mut dyn RngCore; r.next_u32(); r.random::(); - assert_eq!(r.gen_range(0..1), 0); + assert_eq!(r.random_range(0..1), 0); let _c: u8 = Standard.sample(&mut r); } @@ -570,7 +595,7 @@ mod test { let mut r = Box::new(rng) as Box; r.next_u32(); r.random::(); - assert_eq!(r.gen_range(0..1), 0); + assert_eq!(r.random_range(0..1), 0); let _c: u8 = Standard.sample(&mut r); } @@ -584,7 +609,7 @@ mod test { let mut sum: u32 = 0; let mut rng = rng(111); for _ in 0..N { - if rng.gen_ratio(NUM, DENOM) { + if rng.random_ratio(NUM, DENOM) { sum += 1; } } diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index bc7664934ee..fca961f5327 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -136,7 +136,7 @@ thread_local!( /// let mut rng = rand::rng(); /// /// println!("True or false: {}", rng.random::()); -/// println!("A simulated die roll: {}", rng.gen_range(1..=6)); +/// println!("A simulated die roll: {}", rng.random_range(1..=6)); /// # } /// ``` pub fn rng() -> ThreadRng { @@ -185,7 +185,7 @@ mod test { use crate::Rng; let mut r = crate::rng(); r.random::(); - assert_eq!(r.gen_range(0..1), 0); + assert_eq!(r.random_range(0..1), 0); } #[test] diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 4c41c07da44..7e8f53116ce 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -27,17 +27,17 @@ impl CoinFlipper { /// Returns true with a probability of 1 / d /// Uses an expected two bits of randomness /// Panics if d == 0 - pub fn gen_ratio_one_over(&mut self, d: usize) -> bool { + pub fn random_ratio_one_over(&mut self, d: usize) -> bool { debug_assert_ne!(d, 0); - // This uses the same logic as `gen_ratio` but is optimized for the case that + // This uses the same logic as `random_ratio` but is optimized for the case that // the starting numerator is one (which it always is for `Sequence::Choose()`) - // In this case (but not `gen_ratio`), this way of calculating c is always accurate + // In this case (but not `random_ratio`), this way of calculating c is always accurate let c = (usize::BITS - 1 - d.leading_zeros()).min(32); if self.flip_c_heads(c) { let numerator = 1 << c; - self.gen_ratio(numerator, d) + self.random_ratio(numerator, d) } else { false } @@ -46,7 +46,7 @@ impl CoinFlipper { #[inline] /// Returns true with a probability of n / d /// Uses an expected two bits of randomness - fn gen_ratio(&mut self, mut n: usize, d: usize) -> bool { + fn random_ratio(&mut self, mut n: usize, d: usize) -> bool { // Explanation: // We are trying to return true with a probability of n / d // If n >= d, we can just return true diff --git a/src/seq/increasing_uniform.rs b/src/seq/increasing_uniform.rs index 3208c656fb5..10dd48a652a 100644 --- a/src/seq/increasing_uniform.rs +++ b/src/seq/increasing_uniform.rs @@ -41,7 +41,7 @@ impl IncreasingUniform { let next_n = self.n + 1; // There's room for further optimisation here: - // gen_range uses rejection sampling (or other method; see #1196) to avoid bias. + // random_range uses rejection sampling (or other method; see #1196) to avoid bias. // When the initial sample is biased for range 0..bound // it may still be viable to use for a smaller bound // (especially if small biases are considered acceptable). @@ -50,7 +50,7 @@ impl IncreasingUniform { // If the chunk is empty, generate a new chunk let (bound, remaining) = calculate_bound_u32(next_n); // bound = (n + 1) * (n + 2) *..* (n + remaining) - self.chunk = self.rng.gen_range(0..bound); + self.chunk = self.rng.random_range(..bound); // Chunk is a random number in // [0, (n + 1) * (n + 2) *..* (n + remaining) ) diff --git a/src/seq/index.rs b/src/seq/index.rs index b22d6dc4c9c..70231dde2ca 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -430,13 +430,13 @@ fn sample_floyd(rng: &mut R, length: u32, amount: u32) -> IndexVec where R: Rng + ?Sized, { - // Note that the values returned by `rng.gen_range()` can be + // Note that the values returned by `rng.random_range()` can be // inferred from the returned vector by working backwards from // the last entry. This bijection proves the algorithm fair. debug_assert!(amount <= length); let mut indices = Vec::with_capacity(amount as usize); for j in length - amount..length { - let t = rng.gen_range(..=j); + let t = rng.random_range(..=j); if let Some(pos) = indices.iter().position(|&x| x == t) { indices[pos] = j; } @@ -465,7 +465,7 @@ where let mut indices: Vec = Vec::with_capacity(length as usize); indices.extend(0..length); for i in 0..amount { - let j: u32 = rng.gen_range(i..length); + let j: u32 = rng.random_range(i..length); indices.swap(i as usize, j as usize); } indices.truncate(amount as usize); diff --git a/src/seq/iterator.rs b/src/seq/iterator.rs index af9db6917a6..ad96b3baf79 100644 --- a/src/seq/iterator.rs +++ b/src/seq/iterator.rs @@ -68,7 +68,7 @@ pub trait IteratorRandom: Iterator + Sized { return match lower { 0 => None, 1 => self.next(), - _ => self.nth(rng.gen_range(..lower)), + _ => self.nth(rng.random_range(..lower)), }; } @@ -78,7 +78,7 @@ pub trait IteratorRandom: Iterator + Sized { // Continue until the iterator is exhausted loop { if lower > 1 { - let ix = coin_flipper.rng.gen_range(..lower + consumed); + let ix = coin_flipper.rng.random_range(..lower + consumed); let skip = if ix < lower { result = self.nth(ix); lower - (ix + 1) @@ -98,7 +98,7 @@ pub trait IteratorRandom: Iterator + Sized { return result; } consumed += 1; - if coin_flipper.gen_ratio_one_over(consumed) { + if coin_flipper.random_ratio_one_over(consumed) { result = elem; } } @@ -143,7 +143,7 @@ pub trait IteratorRandom: Iterator + Sized { let (lower, _) = self.size_hint(); if lower >= 2 { let highest_selected = (0..lower) - .filter(|ix| coin_flipper.gen_ratio_one_over(consumed + ix + 1)) + .filter(|ix| coin_flipper.random_ratio_one_over(consumed + ix + 1)) .last(); consumed += lower; @@ -161,7 +161,7 @@ pub trait IteratorRandom: Iterator + Sized { return result; } - if coin_flipper.gen_ratio_one_over(consumed + 1) { + if coin_flipper.random_ratio_one_over(consumed + 1) { result = elem; } consumed += 1; @@ -201,7 +201,7 @@ pub trait IteratorRandom: Iterator + Sized { // Continue, since the iterator was not exhausted for (i, elem) in self.enumerate() { - let k = rng.gen_range(..i + 1 + amount); + let k = rng.random_range(..i + 1 + amount); if let Some(slot) = buf.get_mut(k) { *slot = elem; } @@ -237,7 +237,7 @@ pub trait IteratorRandom: Iterator + Sized { // If the iterator stops once, then so do we. if reservoir.len() == amount { for (i, elem) in self.enumerate() { - let k = rng.gen_range(..i + 1 + amount); + let k = rng.random_range(..i + 1 + amount); if let Some(slot) = reservoir.get_mut(k) { *slot = elem; } diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 66459fe1746..82601304da6 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -69,7 +69,7 @@ pub mod index { // Floyd's algorithm let mut indices = [0; N]; for (i, j) in (len - N..len).enumerate() { - let t = rng.gen_range(..j + 1); + let t = rng.random_range(..j + 1); if let Some(pos) = indices[0..i].iter().position(|&x| x == t) { indices[pos] = j; } diff --git a/src/seq/slice.rs b/src/seq/slice.rs index 2b423e46b87..4144f913451 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -56,7 +56,7 @@ pub trait IndexedRandom: Index { if self.is_empty() { None } else { - Some(&self[rng.gen_range(..self.len())]) + Some(&self[rng.random_range(..self.len())]) } } @@ -258,7 +258,7 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { None } else { let len = self.len(); - Some(&mut self[rng.gen_range(..len)]) + Some(&mut self[rng.random_range(..len)]) } } @@ -397,7 +397,7 @@ impl SliceRandom for [T] { // It ensures that the last `amount` elements of the slice // are randomly selected from the whole slice. - // `IncreasingUniform::next_index()` is faster than `Rng::gen_range` + // `IncreasingUniform::next_index()` is faster than `Rng::random_range` // but only works for 32 bit integers // So we must use the slow method if the slice is longer than that. if self.len() < (u32::MAX as usize) { @@ -408,7 +408,7 @@ impl SliceRandom for [T] { } } else { for i in m..self.len() { - let index = rng.gen_range(..i + 1); + let index = rng.random_range(..i + 1); self.swap(i, index); } } From 8b455dd994db5e61092f9b31e82498844b6e7609 Mon Sep 17 00:00:00 2001 From: Marc Pabst <2624210+marcpabst@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:53:37 +0100 Subject: [PATCH 418/443] Add `p()` for `Bernoulli` (#1481) Co-authored-by: Diggory Hardy --- CHANGELOG.md | 1 + src/distr/bernoulli.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 179c889a73b..c96be0ccbe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `rand::distributions` to `rand::distr` (#1470) - The `serde1` feature has been renamed `serde` (#1477) - Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). +- Add `p()` for `Bernoulli` to access probability (#1481) - Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) - Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) diff --git a/src/distr/bernoulli.rs b/src/distr/bernoulli.rs index 84befcc8593..6803518e376 100644 --- a/src/distr/bernoulli.rs +++ b/src/distr/bernoulli.rs @@ -136,6 +136,18 @@ impl Bernoulli { let p_int = ((f64::from(numerator) / f64::from(denominator)) * SCALE) as u64; Ok(Bernoulli { p_int }) } + + #[inline] + /// Returns the probability (`p`) of the distribution. + /// + /// This value may differ slightly from the input due to loss of precision. + pub fn p(&self) -> f64 { + if self.p_int == ALWAYS_TRUE { + 1.0 + } else { + (self.p_int as f64) / SCALE + } + } } impl Distribution for Bernoulli { From 0d320935595147a5cece1ebc79c6d3eb2b02ba40 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Thu, 17 Oct 2024 19:32:27 +0700 Subject: [PATCH 419/443] Use explicit features rather than implicit. (#1473) Also update serde_with and special versions --- CHANGELOG.md | 1 + Cargo.lock.msrv | 382 +++--------------------------------- Cargo.toml | 5 +- rand_core/Cargo.toml | 1 + rand_distr/Cargo.toml | 6 +- rand_distr/src/dirichlet.rs | 8 +- 6 files changed, 42 insertions(+), 361 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c96be0ccbe6..5d78a8d8ca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Fix portability of `rand::distributions::Slice` (#1469) - Rename `rand::distributions` to `rand::distr` (#1470) - The `serde1` feature has been renamed `serde` (#1477) +- The implicit feature `rand_chacha` has been removed. This is enabled by `std_rng`. (#1473) - Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). - Add `p()` for `Bernoulli` to access probability (#1481) - Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) diff --git a/Cargo.lock.msrv b/Cargo.lock.msrv index bf15ecb24d7..36b9ea50d09 100644 --- a/Cargo.lock.msrv +++ b/Cargo.lock.msrv @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -26,18 +17,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anstyle" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" - [[package]] name = "autocfg" version = "1.1.0" @@ -61,18 +40,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "benches" -version = "0.1.0" -dependencies = [ - "criterion", - "criterion-cycles-per-byte", - "rand", - "rand_chacha", - "rand_distr", - "rand_pcg", -] - [[package]] name = "bincode" version = "1.3.3" @@ -88,12 +55,6 @@ version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cc" version = "1.0.90" @@ -119,109 +80,12 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "clap" -version = "4.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" -dependencies = [ - "clap_builder", -] - -[[package]] -name = "clap_builder" -version = "4.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" -dependencies = [ - "anstyle", - "clap_lex", -] - -[[package]] -name = "clap_lex" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" - [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-cycles-per-byte" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5281161544b8f2397e14942c2045efa3446470348121a65c37263f8e76c1e2ff" -dependencies = [ - "criterion", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools", -] - [[package]] name = "crossbeam-channel" version = "0.5.12" @@ -256,12 +120,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "darling" version = "0.20.8" @@ -323,10 +181,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] -name = "equivalent" -version = "1.0.1" +name = "fast_polynomial" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "62eea6ee590b08a5f8b1139f4d6caee195b646d0c07e4b1808fbd5c4dea4829a" +dependencies = [ + "num-traits", +] [[package]] name = "float-ord" @@ -351,27 +212,12 @@ dependencies = [ "wasi", ] -[[package]] -name = "half" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" -dependencies = [ - "crunchy", -] - [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - [[package]] name = "hermit-abi" version = "0.3.9" @@ -420,41 +266,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", "serde", ] -[[package]] -name = "indexmap" -version = "2.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", - "serde", -] - -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.10" @@ -470,6 +285,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lambert_w" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8852c2190439a46c77861aca230080cc9db4064be7f9de8ee81816d6c72c25" +dependencies = [ + "fast_polynomial", + "libm", +] + [[package]] name = "libc" version = "0.2.153" @@ -488,12 +313,6 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" -[[package]] -name = "memchr" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" - [[package]] name = "num-conv" version = "0.1.0" @@ -526,40 +345,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - -[[package]] -name = "plotters" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" - -[[package]] -name = "plotters-svg" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" -dependencies = [ - "plotters-backend", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -667,50 +452,12 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "regex" -version = "1.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" -[[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 = "serde" version = "1.0.197" @@ -744,17 +491,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.7.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" dependencies = [ "base64", "chrono", "hex", - "indexmap 1.9.3", - "indexmap 2.2.5", + "indexmap", "serde", - "serde_derive", "serde_json", "serde_with_macros", "time", @@ -762,9 +507,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.7.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" dependencies = [ "darling", "proc-macro2", @@ -774,10 +519,11 @@ dependencies = [ [[package]] name = "special" -version = "0.10.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89cf0d71ae639fdd8097350bfac415a41aabf1d5ddd356295fdc95f09760382" +checksum = "98d279079c3ddec4e7851337070c1055a18b8f606bba0b1aeb054bc059fc2e27" dependencies = [ + "lambert_w", "libm", ] @@ -829,32 +575,12 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[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" @@ -915,47 +641,6 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[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.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[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" @@ -965,15 +650,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-targets" version = "0.52.4" @@ -1033,18 +709,18 @@ checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "zerocopy" -version = "0.8.0-alpha.6" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db678a6ee512bd06adf35c35be471cae2f9c82a5aed2b5d15e03628c98bddd57" +checksum = "a65238aacd5fb83fb03fcaf94823e71643e937000ec03c46e7da94234b10c870" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.0-alpha.6" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201585ea96d37ee69f2ac769925ca57160cef31acb137c16f38b02b76f4c1e62" +checksum = "3ca22c4ad176b37bd81a565f66635bde3d654fe6832730c3e52e1018ae1655ee" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 2e860f7ffed..1f2eccdc638 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ getrandom = ["rand_core/getrandom"] simd_support = ["zerocopy/simd-nightly"] # Option (enabled by default): enable StdRng -std_rng = ["rand_chacha"] +std_rng = ["dep:rand_chacha"] # Option: enable SmallRng small_rng = [] @@ -56,6 +56,9 @@ small_rng = [] # Note: enabling this option is expected to affect reproducibility of results. unbiased = [] +# Option: enable logging +log = ["dep:log"] + [workspace] members = [ "rand_core", diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 5d7ec72c4cf..13d2b56a471 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -27,6 +27,7 @@ all-features = true [features] std = ["alloc", "getrandom?/std"] alloc = [] # enables Vec and Box support without std +getrandom = ["dep:getrandom"] serde = ["dep:serde"] # enables serde for BlockRng wrapper [dependencies] diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index c3a717a8465..efcfed67ba8 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -29,13 +29,13 @@ alloc = ["rand/alloc"] # feature (default-enabled) will have the same effect. std_math = ["num-traits/std"] -serde = ["dep:serde", "rand/serde"] +serde = ["dep:serde", "dep:serde_with", "rand/serde"] [dependencies] rand = { path = "..", version = "=0.9.0-alpha.1", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } -serde_with = { version = "3.6.1", optional = true } +serde_with = { version = ">= 3.0, <= 3.11", optional = true } [dev-dependencies] rand_pcg = { version = "=0.9.0-alpha.1", path = "../rand_pcg" } @@ -44,4 +44,4 @@ rand = { path = "..", version = "=0.9.0-alpha.1", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.15", features = [ "std" ] } # Special functions for testing distributions -special = "0.10.3" +special = "0.11.0" diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 8ef5c31e84d..ac17fa2e298 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -14,13 +14,13 @@ use crate::{Beta, Distribution, Exp1, Gamma, Open01, StandardNormal}; use core::fmt; use num_traits::{Float, NumCast}; use rand::Rng; -#[cfg(feature = "serde_with")] +#[cfg(feature = "serde")] use serde_with::serde_as; use alloc::{boxed::Box, vec, vec::Vec}; #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde_with", serde_as)] +#[cfg_attr(feature = "serde", serde_as)] struct DirichletFromGamma where F: Float, @@ -171,7 +171,7 @@ where } #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde_with", serde_as)] +#[cfg_attr(feature = "serde", serde_as)] enum DirichletRepr where F: Float, @@ -214,7 +214,7 @@ where /// let samples = dirichlet.sample(&mut rand::rng()); /// println!("{:?} is from a Dirichlet([1.0, 2.0, 3.0]) distribution", samples); /// ``` -#[cfg_attr(feature = "serde_with", serde_as)] +#[cfg_attr(feature = "serde", serde_as)] #[derive(Clone, Debug, PartialEq)] pub struct Dirichlet where From 1aa9a69c206f57cf9563f167d5937b6fa9b378d5 Mon Sep 17 00:00:00 2001 From: Jambo Date: Fri, 18 Oct 2024 02:24:37 +0800 Subject: [PATCH 420/443] =?UTF-8?q?Add=20more=20Kolmogorov=E2=80=93Smirnov?= =?UTF-8?q?=20test=20(#1504)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhance Kolmogorov–Smirnov Test Coverage for Various Distributions --- rand_distr/CHANGELOG.md | 1 + rand_distr/tests/cdf.rs | 700 ++++++++++++++++++++++++++- rand_distr/tests/common/mod.rs | 1 + rand_distr/tests/common/spec_func.rs | 155 ++++++ 4 files changed, 850 insertions(+), 7 deletions(-) create mode 100644 rand_distr/tests/common/mod.rs create mode 100644 rand_distr/tests/common/spec_func.rs diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index a19641752f7..c9ab729be8c 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix panic in Binomial (#1484) - Move some of the computations in Binomial from `sample` to `new` (#1484) - Add Kolmogorov Smirnov test for sampling of `Normal` and `Binomial` (#1494) +- Add Kolmogorov Smirnov test for more distributions (#1504) ### Added - Add plots for `rand_distr` distributions to documentation (#1434) diff --git a/rand_distr/tests/cdf.rs b/rand_distr/tests/cdf.rs index 71b808d241c..8eb22740e2b 100644 --- a/rand_distr/tests/cdf.rs +++ b/rand_distr/tests/cdf.rs @@ -6,13 +6,13 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +mod common; use core::f64; use num_traits::AsPrimitive; use rand::SeedableRng; use rand_distr::{Distribution, Normal}; -use special::Beta; -use special::Primitive; +use special::{Beta, Gamma, Primitive}; // [1] Nonparametric Goodness-of-Fit Tests for Discrete Null Distributions // by Taylor B. Arnold and John W. Emerson @@ -121,11 +121,12 @@ pub fn test_continuous(seed: u64, dist: impl Distribution, cdf: impl Fn(f64 /// Tests a distribution over integers against an analytical CDF. /// The analytical CDF must not have jump points which are not integers. -pub fn test_discrete>( - seed: u64, - dist: impl Distribution, - cdf: impl Fn(i64) -> f64, -) { +pub fn test_discrete(seed: u64, dist: D, cdf: F) +where + I: AsPrimitive, + D: Distribution, + F: Fn(i64) -> f64, +{ let ecdf = sample_ecdf(seed, dist); let ks_statistic = kolmogorov_smirnov_statistic_discrete(ecdf, cdf); @@ -159,6 +160,367 @@ fn normal() { } } +#[test] +fn skew_normal() { + fn cdf(x: f64, location: f64, scale: f64, shape: f64) -> f64 { + let norm = (x - location) / scale; + phi(norm) - 2.0 * owen_t(norm, shape) + } + + let parameters = [(0.0, 1.0, 5.0), (1.0, 10.0, -5.0), (-1.0, 0.00001, 0.0)]; + + for (seed, (location, scale, shape)) in parameters.into_iter().enumerate() { + let dist = rand_distr::SkewNormal::new(location, scale, shape).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, location, scale, shape)); + } +} + +#[test] +fn cauchy() { + fn cdf(x: f64, median: f64, scale: f64) -> f64 { + (1.0 / f64::consts::PI) * ((x - median) / scale).atan() + 0.5 + } + + let parameters = [ + (0.0, 1.0), + (0.0, 0.1), + (1.0, 10.0), + (1.0, 100.0), + (-1.0, 0.00001), + (-1.0, 0.0000001), + ]; + + for (seed, (median, scale)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Cauchy::new(median, scale).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, median, scale)); + } +} + +#[test] +fn uniform() { + fn cdf(x: f64, a: f64, b: f64) -> f64 { + if x < a { + 0.0 + } else if x < b { + (x - a) / (b - a) + } else { + 1.0 + } + } + + let parameters = [(0.0, 1.0), (-1.0, 1.0), (0.0, 100.0), (-100.0, 100.0)]; + + for (seed, (a, b)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Uniform::new(a, b).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, a, b)); + } +} + +#[test] +fn log_normal() { + fn cdf(x: f64, mean: f64, std_dev: f64) -> f64 { + if x <= 0.0 { + 0.0 + } else if x.is_infinite() { + 1.0 + } else { + 0.5 * ((mean - x.ln()) / (std_dev * f64::consts::SQRT_2)).erfc() + } + } + + let parameters = [ + (0.0, 1.0), + (0.0, 0.1), + (0.5, 0.7), + (1.0, 10.0), + (1.0, 100.0), + ]; + + for (seed, (mean, std_dev)) in parameters.into_iter().enumerate() { + let dist = rand_distr::LogNormal::new(mean, std_dev).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, mean, std_dev)); + } +} + +#[test] +fn pareto() { + fn cdf(x: f64, scale: f64, alpha: f64) -> f64 { + if x <= scale { + 0.0 + } else { + 1.0 - (scale / x).powf(alpha) + } + } + + let parameters = [ + (1.0, 1.0), + (1.0, 0.1), + (1.0, 10.0), + (1.0, 100.0), + (0.1, 1.0), + (10.0, 1.0), + (100.0, 1.0), + ]; + + for (seed, (scale, alpha)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Pareto::new(scale, alpha).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, scale, alpha)); + } +} + +#[test] +fn exp() { + fn cdf(x: f64, lambda: f64) -> f64 { + 1.0 - (-lambda * x).exp() + } + + let parameters = [0.5, 1.0, 7.5, 32.0, 100.0]; + + for (seed, lambda) in parameters.into_iter().enumerate() { + let dist = rand_distr::Exp::new(lambda).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, lambda)); + } +} + +#[test] +fn weibull() { + fn cdf(x: f64, lambda: f64, k: f64) -> f64 { + if x < 0.0 { + return 0.0; + } + + 1.0 - (-(x / lambda).powf(k)).exp() + } + + let parameters = [ + (0.5, 1.0), + (1.0, 1.0), + (10.0, 0.1), + (0.1, 10.0), + (15.0, 20.0), + (1000.0, 0.01), + ]; + + for (seed, (lambda, k)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Weibull::new(lambda, k).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, lambda, k)); + } +} + +#[test] +fn gumbel() { + fn cdf(x: f64, mu: f64, beta: f64) -> f64 { + (-(-(x - mu) / beta).exp()).exp() + } + + let parameters = [ + (0.0, 1.0), + (1.0, 2.0), + (-1.0, 0.5), + (10.0, 0.1), + (100.0, 0.0001), + ]; + + for (seed, (mu, beta)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Gumbel::new(mu, beta).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, mu, beta)); + } +} + +#[test] +fn frechet() { + fn cdf(x: f64, alpha: f64, s: f64, m: f64) -> f64 { + if x < m { + return 0.0; + } + + (-((x - m) / s).powf(-alpha)).exp() + } + + let parameters = [ + (0.5, 2.0, 1.0), + (1.0, 1.0, 1.0), + (10.0, 0.1, 1.0), + (100.0, 0.0001, 1.0), + (0.9999, 2.0, 1.0), + ]; + + for (seed, (alpha, s, m)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Frechet::new(m, s, alpha).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, alpha, s, m)); + } +} + +#[test] +fn zeta() { + fn cdf(k: i64, s: f64) -> f64 { + use common::spec_func::zeta_func; + if k < 1 { + return 0.0; + } + + gen_harmonic(k as u64, s) / zeta_func(s) + } + + let parameters = [2.0, 3.7, 5.0, 100.0]; + + for (seed, s) in parameters.into_iter().enumerate() { + let dist = rand_distr::Zeta::new(s).unwrap(); + test_discrete(seed as u64, dist, |k| cdf(k, s)); + } +} + +#[test] +fn zipf() { + fn cdf(k: i64, n: u64, s: f64) -> f64 { + if k < 1 { + return 0.0; + } + if k > n as i64 { + return 1.0; + } + gen_harmonic(k as u64, s) / gen_harmonic(n, s) + } + + let parameters = [(1000, 1.0), (500, 2.0), (1000, 0.5)]; + + for (seed, (n, x)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Zipf::new(n, x).unwrap(); + test_discrete(seed as u64, dist, |k| cdf(k, n, x)); + } +} + +#[test] +fn gamma() { + fn cdf(x: f64, shape: f64, scale: f64) -> f64 { + if x < 0.0 { + return 0.0; + } + + (x / scale).inc_gamma(shape) + } + + let parameters = [ + (0.5, 2.0), + (1.0, 1.0), + (10.0, 0.1), + (100.0, 0.0001), + (0.9999, 2.0), + ]; + + for (seed, (shape, scale)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Gamma::new(shape, scale).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, shape, scale)); + } +} + +#[test] +fn chi_squared() { + fn cdf(x: f64, k: f64) -> f64 { + if x < 0.0 { + return 0.0; + } + + (x / 2.0).inc_gamma(k / 2.0) + } + + let parameters = [0.1, 1.0, 2.0, 10.0, 100.0, 1000.0]; + + for (seed, k) in parameters.into_iter().enumerate() { + let dist = rand_distr::ChiSquared::new(k).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, k)); + } +} +#[test] +fn studend_t() { + fn cdf(x: f64, df: f64) -> f64 { + let h = df / (df + x.powi(2)); + let ib = 0.5 * h.inc_beta(df / 2.0, 0.5, 0.5.ln_beta(df / 2.0)); + if x < 0.0 { + ib + } else { + 1.0 - ib + } + } + + let parameters = [1.0, 10.0, 50.0]; + + for (seed, df) in parameters.into_iter().enumerate() { + let dist = rand_distr::StudentT::new(df).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, df)); + } +} + +#[test] +fn fisher_f() { + fn cdf(x: f64, m: f64, n: f64) -> f64 { + if (m == 1.0 && x <= 0.0) || x < 0.0 { + 0.0 + } else { + let k = m * x / (m * x + n); + let d1 = m / 2.0; + let d2 = n / 2.0; + k.inc_beta(d1, d2, d1.ln_beta(d2)) + } + } + + let parameters = [(1.0, 1.0), (1.0, 2.0), (2.0, 1.0), (50.0, 1.0)]; + + for (seed, (m, n)) in parameters.into_iter().enumerate() { + let dist = rand_distr::FisherF::new(m, n).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, m, n)); + } +} + +#[test] +fn beta() { + fn cdf(x: f64, alpha: f64, beta: f64) -> f64 { + if x < 0.0 { + return 0.0; + } + if x > 1.0 { + return 1.0; + } + let ln_beta_ab = alpha.ln_beta(beta); + x.inc_beta(alpha, beta, ln_beta_ab) + } + + let parameters = [(0.5, 0.5), (2.0, 3.5), (10.0, 1.0), (100.0, 50.0)]; + + for (seed, (alpha, beta)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Beta::new(alpha, beta).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, alpha, beta)); + } +} + +#[test] +fn triangular() { + fn cdf(x: f64, a: f64, b: f64, c: f64) -> f64 { + if x <= a { + 0.0 + } else if a < x && x <= c { + (x - a).powi(2) / ((b - a) * (c - a)) + } else if c < x && x < b { + 1.0 - (b - x).powi(2) / ((b - a) * (b - c)) + } else { + 1.0 + } + } + + let parameters = [ + (0.0, 1.0, 0.0001), + (0.0, 1.0, 0.9999), + (0.0, 1.0, 0.5), + (0.0, 100.0, 50.0), + (-100.0, 100.0, 0.0), + ]; + + for (seed, (a, b, c)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Triangular::new(a, b, c).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, a, b, c)); + } +} + fn binomial_cdf(k: i64, p: f64, n: u64) -> f64 { if k < 0 { return 0.0; @@ -195,3 +557,327 @@ fn binomial() { }); } } + +#[test] +fn geometric() { + fn cdf(k: i64, p: f64) -> f64 { + if k < 0 { + 0.0 + } else { + 1.0 - (1.0 - p).powi(1 + k as i32) + } + } + + let parameters = [0.3, 0.5, 0.7, 0.0000001, 0.9999]; + + for (seed, p) in parameters.into_iter().enumerate() { + let dist = rand_distr::Geometric::new(p).unwrap(); + test_discrete(seed as u64, dist, |k| cdf(k, p)); + } +} + +#[test] +fn hypergeometric() { + fn cdf(x: i64, n: u64, k: u64, n_: u64) -> f64 { + let min = if n_ + k > n { n_ + k - n } else { 0 }; + let max = k.min(n_); + if x < min as i64 { + return 0.0; + } else if x >= max as i64 { + return 1.0; + } + + (min..x as u64 + 1).fold(0.0, |acc, k_| { + acc + (ln_binomial(k, k_) + ln_binomial(n - k, n_ - k_) - ln_binomial(n, n_)).exp() + }) + } + + let parameters = [ + (15, 13, 10), + (25, 15, 5), + (60, 10, 7), + (70, 20, 50), + (100, 50, 10), + // (100, 50, 49), // Fail case + ]; + + for (seed, (n, k, n_)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Hypergeometric::new(n, k, n_).unwrap(); + test_discrete(seed as u64, dist, |x| cdf(x, n, k, n_)); + } +} + +#[test] +fn poisson() { + use rand_distr::Poisson; + fn cdf(k: i64, lambda: f64) -> f64 { + use common::spec_func::gamma_lr; + + if k < 0 || lambda <= 0.0 { + return 0.0; + } + + 1.0 - gamma_lr(k as f64 + 1.0, lambda) + } + let parameters = [ + 0.1, 1.0, 7.5, + // 1e9, passed case but too slow + // 1.844E+19, // fail case + ]; + + for (seed, lambda) in parameters.into_iter().enumerate() { + let dist = Poisson::new(lambda).unwrap(); + test_discrete::, _>(seed as u64, dist, |k| cdf(k, lambda)); + } +} + +fn ln_factorial(n: u64) -> f64 { + (n as f64 + 1.0).lgamma().0 +} + +fn ln_binomial(n: u64, k: u64) -> f64 { + ln_factorial(n) - ln_factorial(k) - ln_factorial(n - k) +} + +fn gen_harmonic(n: u64, m: f64) -> f64 { + match n { + 0 => 1.0, + _ => (0..n).fold(0.0, |acc, x| acc + (x as f64 + 1.0).powf(-m)), + } +} + +/// [1] Patefield, M. (2000). Fast and Accurate Calculation of Owen’s T Function. +/// Journal of Statistical Software, 5(5), 1–25. +/// https://doi.org/10.18637/jss.v005.i05 +/// +/// This function is ported to Rust from the Fortran code provided in the paper +fn owen_t(h: f64, a: f64) -> f64 { + let absh = h.abs(); + let absa = a.abs(); + let ah = absa * absh; + + let mut t; + if absa <= 1.0 { + t = tf(absh, absa, ah); + } else if absh <= 0.67 { + t = 0.25 - znorm1(absh) * znorm1(ah) - tf(ah, 1.0 / absa, absh); + } else { + let normh = znorm2(absh); + let normah = znorm2(ah); + t = 0.5 * (normh + normah) - normh * normah - tf(ah, 1.0 / absa, absh); + } + + if a < 0.0 { + t = -t; + } + + fn tf(h: f64, a: f64, ah: f64) -> f64 { + let rtwopi = 0.159_154_943_091_895_35; + let rrtpi = 0.398_942_280_401_432_7; + + let c2 = [ + 0.999_999_999_999_999_9, + -0.999_999_999_999_888, + 0.999_999_999_982_907_5, + -0.999_999_998_962_825, + 0.999_999_966_604_593_7, + -0.999_999_339_862_724_7, + 0.999_991_256_111_369_6, + -0.999_917_776_244_633_8, + 0.999_428_355_558_701_4, + -0.996_973_117_207_23, + 0.987_514_480_372_753, + -0.959_158_579_805_728_8, + 0.892_463_055_110_067_1, + -0.768_934_259_904_64, + 0.588_935_284_684_846_9, + -0.383_803_451_604_402_55, + 0.203_176_017_010_453, + -8.281_363_160_700_499e-2, + 2.416_798_473_575_957_8e-2, + -4.467_656_666_397_183e-3, + 3.914_116_940_237_383_6e-4, + ]; + + let pts = [ + 3.508_203_967_645_171_6e-3, + 3.127_904_233_803_075_6e-2, + 8.526_682_628_321_945e-2, + 0.162_450_717_308_122_77, + 0.258_511_960_491_254_36, + 0.368_075_538_406_975_3, + 0.485_010_929_056_047, + 0.602_775_141_526_185_7, + 0.714_778_842_177_532_3, + 0.814_755_109_887_601, + 0.897_110_297_559_489_7, + 0.957_238_080_859_442_6, + 0.991_788_329_746_297, + ]; + + let wts = [ + 1.883_143_811_532_350_3e-2, + 1.856_708_624_397_765e-2, + 1.804_209_346_122_338_5e-2, + 1.726_382_960_639_875_2e-2, + 1.624_321_997_598_985_8e-2, + 1.499_459_203_411_670_5e-2, + 1.353_547_446_966_209e-2, + 1.188_635_160_582_016_5e-2, + 1.007_037_724_277_743_2e-2, + 8.113_054_574_229_958e-3, + 6.041_900_952_847_024e-3, + 3.886_221_701_074_205_7e-3, + 1.679_303_108_454_609e-3, + ]; + + let hrange = [ + 0.02, 0.06, 0.09, 0.125, 0.26, 0.4, 0.6, 1.6, 1.7, 2.33, 2.4, 3.36, 3.4, 4.8, + ]; + let arange = [0.025, 0.09, 0.15, 0.36, 0.5, 0.9, 0.99999]; + + let select = [ + [1, 1, 2, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 9], + [1, 2, 2, 3, 3, 5, 5, 14, 14, 15, 15, 16, 16, 16, 9], + [2, 2, 3, 3, 3, 5, 5, 15, 15, 15, 15, 16, 16, 16, 10], + [2, 2, 3, 5, 5, 5, 5, 7, 7, 16, 16, 16, 16, 16, 10], + [2, 3, 3, 5, 5, 6, 6, 8, 8, 17, 17, 17, 12, 12, 11], + [2, 3, 5, 5, 5, 6, 6, 8, 8, 17, 17, 17, 12, 12, 12], + [2, 3, 4, 4, 6, 6, 8, 8, 17, 17, 17, 17, 17, 12, 12], + [2, 3, 4, 4, 6, 6, 18, 18, 18, 18, 17, 17, 17, 12, 12], + ]; + + let ihint = hrange.iter().position(|&r| h < r).unwrap_or(14); + + let iaint = arange.iter().position(|&r| a < r).unwrap_or(7); + + let icode = select[iaint][ihint]; + let m = [ + 2, 3, 4, 5, 7, 10, 12, 18, 10, 20, 30, 20, 4, 7, 8, 20, 13, 0, + ][icode - 1]; + let method = [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5, 6][icode - 1]; + + match method { + 1 => { + let hs = -0.5 * h * h; + let dhs = hs.exp(); + let as_ = a * a; + let mut j = 1; + let mut jj = 1; + let mut aj = rtwopi * a; + let mut tf = rtwopi * a.atan(); + let mut dj = dhs - 1.0; + let mut gj = hs * dhs; + loop { + tf += dj * aj / (jj as f64); + if j >= m { + return tf; + } + j += 1; + jj += 2; + aj *= as_; + dj = gj - dj; + gj *= hs / (j as f64); + } + } + 2 => { + let maxii = m + m + 1; + let mut ii = 1; + let mut tf = 0.0; + let hs = h * h; + let as_ = -a * a; + let mut vi = rrtpi * a * (-0.5 * ah * ah).exp(); + let mut z = znorm1(ah) / h; + let y = 1.0 / hs; + loop { + tf += z; + if ii >= maxii { + tf *= rrtpi * (-0.5 * hs).exp(); + return tf; + } + z = y * (vi - (ii as f64) * z); + vi *= as_; + ii += 2; + } + } + 3 => { + let mut i = 1; + let mut ii = 1; + let mut tf = 0.0; + let hs = h * h; + let as_ = a * a; + let mut vi = rrtpi * a * (-0.5 * ah * ah).exp(); + let mut zi = znorm1(ah) / h; + let y = 1.0 / hs; + loop { + tf += zi * c2[i - 1]; + if i > m { + tf *= rrtpi * (-0.5 * hs).exp(); + return tf; + } + zi = y * ((ii as f64) * zi - vi); + vi *= as_; + i += 1; + ii += 2; + } + } + 4 => { + let maxii = m + m + 1; + let mut ii = 1; + let mut tf = 0.0; + let hs = h * h; + let as_ = -a * a; + let mut ai = rtwopi * a * (-0.5 * hs * (1.0 - as_)).exp(); + let mut yi = 1.0; + loop { + tf += ai * yi; + if ii >= maxii { + return tf; + } + ii += 2; + yi = (1.0 - hs * yi) / (ii as f64); + ai *= as_; + } + } + 5 => { + let mut tf = 0.0; + let as_ = a * a; + let hs = -0.5 * h * h; + for i in 0..m { + let r = 1.0 + as_ * pts[i]; + tf += wts[i] * (hs * r).exp() / r; + } + tf *= a; + tf + } + 6 => { + let normh = znorm2(h); + let mut tf = 0.5 * normh * (1.0 - normh); + let y = 1.0 - a; + let r = (y / (1.0 + a)).atan(); + if r != 0.0 { + tf -= rtwopi * r * (-0.5 * y * h * h / r).exp(); + } + tf + } + _ => 0.0, + } + } + + // P(0 ≤ Z ≤ x) + fn znorm1(x: f64) -> f64 { + phi(x) - 0.5 + } + + // P(x ≤ Z < ∞) + fn znorm2(x: f64) -> f64 { + 1.0 - phi(x) + } + + t +} + +/// standard normal cdf +fn phi(x: f64) -> f64 { + normal_cdf(x, 0.0, 1.0) +} diff --git a/rand_distr/tests/common/mod.rs b/rand_distr/tests/common/mod.rs new file mode 100644 index 00000000000..4fa8db7de6a --- /dev/null +++ b/rand_distr/tests/common/mod.rs @@ -0,0 +1 @@ +pub mod spec_func; diff --git a/rand_distr/tests/common/spec_func.rs b/rand_distr/tests/common/spec_func.rs new file mode 100644 index 00000000000..bd39a79fb47 --- /dev/null +++ b/rand_distr/tests/common/spec_func.rs @@ -0,0 +1,155 @@ +// MIT License + +// Copyright (c) 2016 Michael Ma + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +/// https://docs.rs/statrs/latest/src/statrs/function/gamma.rs.html#260-347 +use special::Primitive; + +pub fn gamma_lr(a: f64, x: f64) -> f64 { + if a.is_nan() || x.is_nan() { + return f64::NAN; + } + + if a <= 0.0 || a == f64::INFINITY { + panic!("a must be positive and finite"); + } + if x <= 0.0 || x == f64::INFINITY { + panic!("x must be positive and finite"); + } + + const ACC: f64 = 0.0000000000000011102230246251565; + + if a.abs() < ACC { + return 1.0; + } + + if x.abs() < ACC { + return 0.0; + } + + let eps = 0.000000000000001; + let big = 4503599627370496.0; + let big_inv = 2.220_446_049_250_313e-16; + + let ax = a * x.ln() - x - a.lgamma().0; + if ax < -709.782_712_893_384 { + if a < x { + return 1.0; + } + return 0.0; + } + if x <= 1.0 || x <= a { + let mut r2 = a; + let mut c2 = 1.0; + let mut ans2 = 1.0; + loop { + r2 += 1.0; + c2 *= x / r2; + ans2 += c2; + + if c2 / ans2 <= eps { + break; + } + } + return ax.exp() * ans2 / a; + } + + let mut y = 1.0 - a; + let mut z = x + y + 1.0; + let mut c = 0; + + let mut p3 = 1.0; + let mut q3 = x; + let mut p2 = x + 1.0; + let mut q2 = z * x; + let mut ans = p2 / q2; + + loop { + y += 1.0; + z += 2.0; + c += 1; + let yc = y * f64::from(c); + + let p = p2 * z - p3 * yc; + let q = q2 * z - q3 * yc; + + p3 = p2; + p2 = p; + q3 = q2; + q2 = q; + + if p.abs() > big { + p3 *= big_inv; + p2 *= big_inv; + q3 *= big_inv; + q2 *= big_inv; + } + + if q != 0.0 { + let nextans = p / q; + let error = ((ans - nextans) / nextans).abs(); + ans = nextans; + + if error <= eps { + break; + } + } + } + 1.0 - ax.exp() * ans +} + +/// https://docs.rs/spfunc/latest/src/spfunc/zeta.rs.html#20-43 +pub fn zeta_func(s: f64) -> f64 { + fn update_akn(akn: &mut Vec, s: f64) { + let n = akn.len() - 1; + + let n1 = n as f64 + 1.0; + + akn.iter_mut().enumerate().for_each(|(k, a)| { + let num = n1; + let den = n + 1 - k; + *a *= num / den as f64; + }); + let p1 = -1.0 / n1; + let p2 = (n1 / (n1 + 1.0)).powf(s); + + akn.push(p1 * p2 * akn[n]); + } + + if s == 1.0 { + return f64::INFINITY; + } + + let mut akn = vec![1.0]; + let mut two_pow = 0.5; + let head = 1.0 / (1.0 - 2.0.powf(1.0 - s)); + let mut tail_prev = 0.0; + let mut tail = two_pow * akn[0]; + + while (tail - tail_prev).abs() >= f64::EPSILON { + update_akn(&mut akn, s); + two_pow /= 2.0; + tail_prev = tail; + tail += two_pow * akn.iter().sum::(); + } + + head * tail +} From 0c36c6ca591b6e2f49bcdda809314df830b83653 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 24 Oct 2024 18:13:39 +0100 Subject: [PATCH 421/443] Remove u64 support for Poisson (#1517) --- CHANGELOG.md | 5 +---- rand_distr/CHANGELOG.md | 3 +++ rand_distr/src/poisson.rs | 19 +++++++++++-------- rand_distr/src/zeta.rs | 11 +++++++++++ rand_distr/src/zipf.rs | 7 +++++++ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d78a8d8ca9..71c7353174c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,14 +21,11 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `rand::distributions` to `rand::distr` (#1470) - The `serde1` feature has been renamed `serde` (#1477) - The implicit feature `rand_chacha` has been removed. This is enabled by `std_rng`. (#1473) -- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). +- Mark `WeightError` as `#[non_exhaustive]` (#1480). - Add `p()` for `Bernoulli` to access probability (#1481) - Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) -- Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) - Improve SmallRng initialization performance (#1482) -- Implement `Distribution` for `Poisson` (#1498) -- Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) - Rename `Rng::gen_iter` to `random_iter` (#1500) - Rename `rand::thread_rng()` to `rand::rng()`, and remove from the prelude (#1506) - Remove `rand::random()` from the prelude (#1506) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index c9ab729be8c..8e6cfff686b 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move some of the computations in Binomial from `sample` to `new` (#1484) - Add Kolmogorov Smirnov test for sampling of `Normal` and `Binomial` (#1494) - Add Kolmogorov Smirnov test for more distributions (#1504) +- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). +- Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) +- Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) ### Added - Add plots for `rand_distr` distributions to documentation (#1434) diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index f06c29aef41..20b41ace64a 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -39,6 +39,17 @@ use rand::Rng; /// let v: f64 = poi.sample(&mut rand::rng()); /// println!("{} is from a Poisson(2) distribution", v); /// ``` +/// +/// # Integer vs FP return type +/// +/// This implementation uses floating-point (FP) logic internally. +/// +/// Due to the parameter limit λ < [Self::MAX_LAMBDA], it +/// statistically impossible to sample a value larger [`u64::MAX`]. As such, it +/// is reasonable to cast generated samples to `u64` using `as`: +/// `distr.sample(&mut rng) as u64` (and memory safe since Rust 1.45). +/// Similarly, when `λ < 4.2e9` it can be safely assumed that samples are less +/// than `u32::MAX`. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Poisson(Method) @@ -238,14 +249,6 @@ where } } -impl Distribution for Poisson { - #[inline] - fn sample(&self, rng: &mut R) -> u64 { - // `as` from float to int saturates - as Distribution>::sample(self, rng) as u64 - } -} - #[cfg(test)] mod test { use super::*; diff --git a/rand_distr/src/zeta.rs b/rand_distr/src/zeta.rs index 3c2f55546e5..e06208c94ea 100644 --- a/rand_distr/src/zeta.rs +++ b/rand_distr/src/zeta.rs @@ -40,6 +40,17 @@ use rand::{distr::OpenClosed01, Rng}; /// println!("{}", val); /// ``` /// +/// # Integer vs FP return type +/// +/// This implementation uses floating-point (FP) logic internally, which can +/// potentially generate very large samples (exceeding e.g. `u64::MAX`). +/// +/// It is *safe* to cast such results to an integer type using `as` +/// (e.g. `distr.sample(&mut rng) as u64`), since such casts are saturating +/// (e.g. `2f64.powi(64) as u64 == u64::MAX`). It is up to the user to +/// determine whether this potential loss of accuracy is acceptable +/// (this determination may depend on the distribution's parameters). +/// /// # Notes /// /// The zeta distribution has no upper limit. Sampled values may be infinite. diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 0a56fdf8182..9d98458db08 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -39,6 +39,13 @@ use rand::Rng; /// println!("{}", val); /// ``` /// +/// # Integer vs FP return type +/// +/// This implementation uses floating-point (FP) logic internally. It may be +/// expected that the samples are no greater than `n`, thus it is reasonable to +/// cast generated samples to any integer type which can also represent `n` +/// (e.g. `distr.sample(&mut rng) as u64`). +/// /// # Implementation details /// /// Implemented via [rejection sampling](https://en.wikipedia.org/wiki/Rejection_sampling), From 24b9cc38ea81b3bbbb17e7e479d798c48081a158 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 28 Oct 2024 14:20:59 +0000 Subject: [PATCH 422/443] README: rand is not a crypto library (#1514) Closes #1358 by documenting what Rand is not. Co-authored-by: Dan --- README.md | 40 +++++++++++++++++-------------- SECURITY.md | 43 ++++++++++++++++++++++----------- rand_core/src/os.rs | 8 +++---- src/distr/uniform.rs | 48 +++++++++++++++++++------------------ src/distr/uniform_float.rs | 17 +++++++------ src/distr/uniform_int.rs | 7 ++++++ src/rngs/thread.rs | 49 +++++++++++++++++++++++++------------- 7 files changed, 130 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 1dbe4c55f12..58f363896b3 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,20 @@ [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) [![API](https://docs.rs/rand/badge.svg)](https://docs.rs/rand) -Rand is a Rust library supporting random generators: +Rand is a set of crates supporting (pseudo-)random generators: -- A standard RNG trait: [`rand_core::RngCore`](https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html) -- Fast implementations of the best-in-class [cryptographic](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and - [non-cryptographic](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) -- [`rand::rng`](https://docs.rs/rand/latest/rand/fn.rng.html) is an asymtotically-fast, reasonably secure generator available on all `std` targets -- Secure seeding via the [`getrandom` crate](https://crates.io/crates/getrandom) +- Built over a standard RNG trait: [`rand_core::RngCore`](https://docs.rs/rand_core/latest/rand_core/trait.RngCore.html) +- With fast implementations of both [strong](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and + [small](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) +- [`rand::rng`](https://docs.rs/rand/latest/rand/fn.rng.html) is an asymptotically-fast, automatically-seeded and reasonably strong generator available on all `std` targets +- Direct support for seeding generators from the [`getrandom` crate](https://crates.io/crates/getrandom) -Supporting random value generation and random processes: +With broad support for random value generation and random processes: -- [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html) random value generation -- Ranged [`Uniform`](https://docs.rs/rand/latest/rand/distributions/struct.Uniform.html) number generation for many types -- A flexible [`distributions`](https://docs.rs/rand/*/rand/distr/index.html) module -- Samplers for a large number of random number distributions via our own +- [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html) random value sampling, + [`Uniform`](https://docs.rs/rand/latest/rand/distributions/struct.Uniform.html)-ranged value sampling + and [more](https://docs.rs/rand/latest/rand/distr/index.html) +- Samplers for a large number of non-uniform random number distributions via our own [`rand_distr`](https://docs.rs/rand_distr) and via the [`statrs`](https://docs.rs/statrs/0.13.0/statrs/) - Random processes (mostly choose and shuffle) via [`rand::seq`](https://docs.rs/rand/latest/rand/seq/index.html) traits @@ -28,19 +28,23 @@ All with: - [Portably reproducible output](https://rust-random.github.io/book/portability.html) - `#[no_std]` compatibility (partial) -- *Many* performance optimisations +- *Many* performance optimisations thanks to contributions from the wide + user-base -It's also worth pointing out what Rand *is not*: +Rand **is not**: -- Small. Most low-level crates are small, but the higher-level `rand` and - `rand_distr` each contain a lot of functionality. +- Small (LOC). Most low-level crates are small, but the higher-level `rand` + and `rand_distr` each contain a lot of functionality. - Simple (implementation). We have a strong focus on correctness, speed and flexibility, but not simplicity. If you prefer a small-and-simple library, there are alternatives including [fastrand](https://crates.io/crates/fastrand) and [oorandom](https://crates.io/crates/oorandom). -- Slow. We take performance seriously, with considerations also for set-up - time of new distributions, commonly-used parameters, and parameters of the - current sampler. +- A cryptography library. Rand provides functionality for generating + unpredictable random data (potentially applicable depending on requirements) + but does not provide high-level cryptography functionality. + +Rand is a community project and cannot provide legally-binding guarantees of +security. Documentation: diff --git a/SECURITY.md b/SECURITY.md index 356fbe879de..26cf7c12fc5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,31 +1,46 @@ # Security Policy -## No guarantees +## Disclaimer -Support is provided on a best-effort bases only. -No binding guarantees can be provided. +Rand is a community project and cannot provide legally-binding guarantees of +security. ## Security premises -Rand provides the trait `rand_core::CryptoRng` aka `rand::CryptoRng` as a marker -trait. Generators implementing `RngCore` *and* `CryptoRng`, and given the -additional constraints that: +### Marker traits + +Rand provides the marker traits `CryptoRng`, `TryCryptoRng` and +`CryptoBlockRng`. Generators implementing one of these traits and used in a way +which meets the following additional constraints: - Instances of seedable RNGs (those implementing `SeedableRng`) are constructed with cryptographically secure seed values -- The state (memory) of the RNG and its seed value are not be exposed +- The state (memory) of the RNG and its seed value are not exposed are expected to provide the following: -- An attacker can gain no advantage over chance (50% for each bit) in - predicting the RNG output, even with full knowledge of all prior outputs. +- An attacker cannot predict the output with more accuracy than what would be + expected through pure chance since each possible output value of any method + under the above traits which generates output bytes (including + `RngCore::next_u32`, `RngCore::next_u64`, `RngCore::fill_bytes`, + `TryRngCore::try_next_u32`, `TryRngCore::try_next_u64`, + `TryRngCore::try_fill_bytes` and `BlockRngCore::generate`) should be equally + likely +- Knowledge of prior outputs from the generator does not aid an attacker in + predicting future outputs + +### Specific generators + +`OsRng` is a stateless "generator" implemented via [getrandom]. As such, it has +no possible state to leak and cannot be improperly seeded. + +`ThreadRng` will periodically reseed itself, thus placing an upper bound on the +number of bits of output from an instance before any advantage an attacker may +have gained through state-compromising side-channel attacks is lost. -For some RNGs, notably `OsRng`, `ThreadRng` and those wrapped by `ReseedingRng`, -we provide limited mitigations against side-channel attacks: +[getrandom]: https://crates.io/crates/getrandom -- After the state (memory) of an RNG is leaked, there is an upper-bound on the - number of bits of output by the RNG before prediction of output by an - observer again becomes computationally-infeasible +### Distributions Additionally, derivations from such an RNG (including the `Rng` trait, implementations of the `Distribution` trait, and `seq` algorithms) should not diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index 86ae462e5a3..78b689bc021 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -11,10 +11,9 @@ use crate::{TryCryptoRng, TryRngCore}; use getrandom::getrandom; -/// A random number generator that retrieves randomness from the -/// operating system. +/// An interface over the operating-system's random data source /// -/// This is a zero-sized struct. It can be freely constructed with `OsRng`. +/// This is a zero-sized struct. It can be freely constructed with just `OsRng`. /// /// The implementation is provided by the [getrandom] crate. Refer to /// [getrandom] documentation for details. @@ -32,7 +31,8 @@ use getrandom::getrandom; /// /// After the first successful call, it is highly unlikely that failures or /// significant delays will occur (although performance should be expected to -/// be much slower than a user-space PRNG). +/// be much slower than a user-space +/// [PRNG](https://rust-random.github.io/book/guide-gen.html#pseudo-random-number-generators)). /// /// # Usage example /// ``` diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 2ab4f56b826..ac3e1676ada 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -11,17 +11,7 @@ //! //! [`Uniform`] is the standard distribution to sample uniformly from a range; //! e.g. `Uniform::new_inclusive(1, 6).unwrap()` can sample integers from 1 to 6, like a -//! standard die. [`Rng::random_range`] supports any type supported by [`Uniform`]. -//! -//! This distribution is provided with support for several primitive types -//! (all integer and floating-point types) as well as [`std::time::Duration`], -//! and supports extension to user-defined types via a type-specific *back-end* -//! implementation. -//! -//! The types [`UniformInt`], [`UniformFloat`] and [`UniformDuration`] are the -//! back-ends supporting sampling from primitive integer and floating-point -//! ranges as well as from [`std::time::Duration`]; these types do not normally -//! need to be used directly (unless implementing a derived back-end). +//! standard die. [`Rng::random_range`] is implemented over [`Uniform`]. //! //! # Example usage //! @@ -151,26 +141,38 @@ use serde::{Deserialize, Serialize}; /// Sample values uniformly between two bounds. /// +/// # Construction +/// /// [`Uniform::new`] and [`Uniform::new_inclusive`] construct a uniform -/// distribution sampling from the given range; these functions may do extra -/// work up front to make sampling of multiple values faster. If only one sample -/// from the range is required, [`Rng::random_range`] can be more efficient. +/// distribution sampling from the given `low` and `high` limits. `Uniform` may +/// also be constructed via [`TryFrom`] as in `Uniform::try_from(1..=6).unwrap()`. +/// +/// Constructors may do extra work up front to allow faster sampling of multiple +/// values. Where only a single sample is required it is suggested to use +/// [`Rng::random_range`] or one of the `sample_single` methods instead. /// /// When sampling from a constant range, many calculations can happen at /// compile-time and all methods should be fast; for floating-point ranges and /// the full range of integer types, this should have comparable performance to /// the `Standard` distribution. /// -/// Steps are taken to avoid bias, which might be present in naive -/// implementations; for example `rng.gen::() % 170` samples from the range -/// `[0, 169]` but is twice as likely to select numbers less than 85 than other -/// values. Further, the implementations here give more weight to the high-bits -/// generated by the RNG than the low bits, since with some RNGs the low-bits -/// are of lower quality than the high bits. +/// # Provided implementations /// -/// Implementations must sample in `[low, high)` range for -/// `Uniform::new(low, high)`, i.e., excluding `high`. In particular, care must -/// be taken to ensure that rounding never results values `< low` or `>= high`. +/// - `char` ([`UniformChar`]): samples a range over the implementation for `u32` +/// - `f32`, `f64` ([`UniformFloat`]): samples approximately uniformly within a +/// range; bias may be present in the least-significant bit of the significand +/// and the limits of the input range may be sampled even when an open +/// (exclusive) range is used +/// - Integer types ([`UniformInt`]) may show a small bias relative to the +/// expected uniform distribution of output. In the worst case, bias affects +/// 1 in `2^n` samples where n is 56 (`i8` and `u8`), 48 (`i16` and `u16`), 96 +/// (`i32` and `u32`), 64 (`i64` and `u64`), 128 (`i128` and `u128`). +/// The `unbiased` feature flag fixes this bias. +/// - `usize` ([`UniformUsize`]) is handled specially, using the `u32` +/// implementation where possible to enable portable results across 32-bit and +/// 64-bit CPU architectures. +/// - `Duration` ([`UniformDuration`]): samples a range over the implementation +/// for `u32` or `u64` /// /// # Example /// diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index 82fd68bbc88..99bf2bf6011 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -29,14 +29,17 @@ use serde::{Deserialize, Serialize}; /// /// # Implementation notes /// -/// Instead of generating a float in the `[0, 1)` range using [`Standard`], the -/// `UniformFloat` implementation converts the output of an PRNG itself. This -/// way one or two steps can be optimized out. +/// `UniformFloat` implementations convert RNG output to a float in the range +/// `[1, 2)` via transmutation, map this to `[0, 1)`, then scale and translate +/// to the desired range. Values produced this way have what equals 23 bits of +/// random digits for an `f32` and 52 for an `f64`. /// -/// The floats are first converted to a value in the `[1, 2)` interval using a -/// transmute-based method, and then mapped to the expected range with a -/// multiply and addition. Values produced this way have what equals 23 bits of -/// random digits for an `f32`, and 52 for an `f64`. +/// # Bias and range errors +/// +/// Bias may be expected within the least-significant bit of the significand. +/// It is not guaranteed that exclusive limits of a range are respected; i.e. +/// when sampling the range `[a, b)` it is not guaranteed that `b` is never +/// sampled. /// /// [`new`]: UniformSampler::new /// [`new_inclusive`]: UniformSampler::new_inclusive diff --git a/src/distr/uniform_int.rs b/src/distr/uniform_int.rs index b53ca367b9f..4fe07707bd3 100644 --- a/src/distr/uniform_int.rs +++ b/src/distr/uniform_int.rs @@ -58,6 +58,13 @@ use serde::{Deserialize, Serialize}; /// multiply by `range`, the result is in the high word. Then comparing the low /// word against `zone` makes sure our distribution is uniform. /// +/// # Bias +/// +/// Unless the `unbiased` feature flag is used, outputs may have a small bias. +/// In the worst case, bias affects 1 in `2^n` samples where n is +/// 56 (`i8` and `u8`), 48 (`i16` and `u16`), 96 (`i32` and `u32`), 64 (`i64` +/// and `u64`), 128 (`i128` and `u128`). +/// /// [`Uniform`]: super::Uniform #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index fca961f5327..64ca0e1f761 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -41,16 +41,36 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// A reference to the thread-local generator /// /// This type is a reference to a lazily-initialized thread-local generator. -/// An instance can be obtained via [`rand::rng()`][crate::rng())] or via -/// `ThreadRng::default()`. +/// An instance can be obtained via [`rand::rng()`][crate::rng()] or via +/// [`ThreadRng::default()`]. /// The handle cannot be passed between threads (is not `Send` or `Sync`). /// -/// `ThreadRng` uses the same CSPRNG as [`StdRng`], ChaCha12. As with -/// [`StdRng`], the algorithm may be changed, subject to reasonable expectations -/// of security and performance. +/// # Security /// -/// `ThreadRng` is automatically seeded from [`OsRng`] with periodic reseeding -/// (every 64 kiB — see [`ReseedingRng`] documentation for details). +/// Security must be considered relative to a threat model and validation +/// requirements. The Rand project can provide no guarantee of fitness for +/// purpose. The design criteria for `ThreadRng` are as follows: +/// +/// - Automatic seeding via [`OsRng`] and periodically thereafter (see +/// ([`ReseedingRng`] documentation). Limitation: there is no automatic +/// reseeding on process fork (see [below](#fork)). +/// - A rigorusly analyzed, unpredictable (cryptographic) pseudo-random generator +/// (see [the book on security](https://rust-random.github.io/book/guide-rngs.html#security)). +/// The currently selected algorithm is ChaCha (12-rounds). +/// See also [`StdRng`] documentation. +/// - Not to leak internal state through [`Debug`] or serialization +/// implementations. +/// - No further protections exist to in-memory state. In particular, the +/// implementation is not required to zero memory on exit (of the process or +/// thread). (This may change in the future.) +/// - Be fast enough for general-purpose usage. Note in particular that +/// `ThreadRng` is designed to be a "fast, reasonably secure generator" +/// (where "reasonably secure" implies the above criteria). +/// +/// We leave it to the user to determine whether this generator meets their +/// security requirements. For an alternative, see [`OsRng`]. +/// +/// # Fork /// /// `ThreadRng` is not automatically reseeded on fork. It is recommended to /// explicitly call [`ThreadRng::reseed`] immediately after a fork, for example: @@ -68,13 +88,6 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64; /// from an interrupt (e.g. a fork handler) unless it can be guaranteed that no /// other method on the same `ThreadRng` is currently executing. /// -/// Security must be considered relative to a threat model and validation -/// requirements. `ThreadRng` attempts to meet basic security considerations -/// for producing unpredictable random numbers: use a CSPRNG, use a -/// recommended platform-specific seed ([`OsRng`]), and avoid -/// leaking internal secrets e.g. via [`Debug`] implementation or serialization. -/// Memory is not zeroized on drop. -/// /// [`ReseedingRng`]: crate::rngs::ReseedingRng /// [`StdRng`]: crate::rngs::StdRng #[derive(Clone)] @@ -115,9 +128,9 @@ thread_local!( } ); -/// Access a local, pre-initialized generator +/// Access a fast, pre-initialized generator /// -/// This is a reasonably fast unpredictable thread-local instance of [`ThreadRng`]. +/// This is a handle to the local [`ThreadRng`]. /// /// See also [`crate::rngs`] for alternatives. /// @@ -139,6 +152,10 @@ thread_local!( /// println!("A simulated die roll: {}", rng.random_range(1..=6)); /// # } /// ``` +/// +/// # Security +/// +/// Refer to [`ThreadRng#Security`]. pub fn rng() -> ThreadRng { let rng = THREAD_RNG_KEY.with(|t| t.clone()); ThreadRng { rng } From 585b29f2a9b21170c56fc9a0e29cff664491561c Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Thu, 31 Oct 2024 05:12:55 -0400 Subject: [PATCH 423/443] Add top-level helper functions (#1488) Adds random_iter, random_range, random_bool, random_ratio, fill. See also #989, #1503. Co-authored-by: Diggory Hardy --- src/lib.rs | 148 ++++++++++++++++++++++++++++++++++++++++---- src/rng.rs | 4 +- src/seq/iterator.rs | 9 +++ 3 files changed, 147 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 833fe0c0e46..f9092925471 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,9 +121,9 @@ pub use rng::{Fill, Rng}; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] use crate::distr::{Distribution, Standard}; -/// Generates a random value using the thread-local random number generator. +/// Generate a random value using the thread-local random number generator. /// -/// This function is simply a shortcut for `rand::rng().gen()`: +/// This function is shorthand for [rng()].[random()](Rng::random): /// /// - See [`ThreadRng`] for documentation of the generator and security /// - See [`Standard`] for documentation of supported types and distributions @@ -142,21 +142,15 @@ use crate::distr::{Distribution, Standard}; /// } /// ``` /// -/// If you're calling `random()` in a loop, caching the generator as in the -/// following example can increase performance. +/// If you're calling `random()` repeatedly, consider using a local `rng` +/// handle to save an initialization-check on each usage: /// /// ``` -/// use rand::Rng; +/// use rand::Rng; // provides the `random` method /// -/// let mut v = vec![1, 2, 3]; -/// -/// for x in v.iter_mut() { -/// *x = rand::random() -/// } -/// -/// // can be made faster by caching rand::rng +/// let mut rng = rand::rng(); // a local handle to the generator /// -/// let mut rng = rand::rng(); +/// let mut v = vec![1, 2, 3]; /// /// for x in v.iter_mut() { /// *x = rng.random(); @@ -174,6 +168,127 @@ where rng().random() } +/// Return an iterator over [`random()`] variates +/// +/// This function is shorthand for +/// [rng()].[random_iter](Rng::random_iter)(). +/// +/// # Example +/// +/// ``` +/// let v: Vec = rand::random_iter().take(5).collect(); +/// println!("{v:?}"); +/// ``` +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +pub fn random_iter() -> distr::DistIter +where + Standard: Distribution, +{ + rng().random_iter() +} + +/// Generate a random value in the given range using the thread-local random number generator. +/// +/// This function is shorthand for +/// [rng()].[random_range](Rng::random_range)(range). +/// +/// # Example +/// +/// ``` +/// let y: f32 = rand::random_range(0.0..=1e9); +/// println!("{}", y); +/// +/// let words: Vec<&str> = "Mary had a little lamb".split(' ').collect(); +/// println!("{}", words[rand::random_range(..words.len())]); +/// ``` +/// Note that the first example can also be achieved (without `collect`'ing +/// to a `Vec`) using [`seq::IteratorRandom::choose`]. +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +pub fn random_range(range: R) -> T +where + T: distr::uniform::SampleUniform, + R: distr::uniform::SampleRange, +{ + rng().random_range(range) +} + +/// Return a bool with a probability `p` of being true. +/// +/// This function is shorthand for +/// [rng()].[random_bool](Rng::random_bool)(p). +/// +/// # Example +/// +/// ``` +/// println!("{}", rand::random_bool(1.0 / 3.0)); +/// ``` +/// +/// # Panics +/// +/// If `p < 0` or `p > 1`. +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn random_bool(p: f64) -> bool { + rng().random_bool(p) +} + +/// Return a bool with a probability of `numerator/denominator` of being +/// true. +/// +/// That is, `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of +/// returning true. If `numerator == denominator`, then the returned value +/// is guaranteed to be `true`. If `numerator == 0`, then the returned +/// value is guaranteed to be `false`. +/// +/// See also the [`Bernoulli`] distribution, which may be faster if +/// sampling from the same `numerator` and `denominator` repeatedly. +/// +/// This function is shorthand for +/// [rng()].[random_ratio](Rng::random_ratio)(numerator, denominator). +/// +/// # Panics +/// +/// If `denominator == 0` or `numerator > denominator`. +/// +/// # Example +/// +/// ``` +/// println!("{}", rand::random_ratio(2, 3)); +/// ``` +/// +/// [`Bernoulli`]: distr::Bernoulli +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn random_ratio(numerator: u32, denominator: u32) -> bool { + rng().random_ratio(numerator, denominator) +} + +/// Fill any type implementing [`Fill`] with random data +/// +/// This function is shorthand for +/// [rng()].[fill](Rng::fill)(dest). +/// +/// # Example +/// +/// ``` +/// let mut arr = [0i8; 20]; +/// rand::fill(&mut arr[..]); +/// ``` +/// +/// Note that you can instead use [`random()`] to generate an array of random +/// data, though this is slower for small elements (smaller than the RNG word +/// size). +#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[inline] +#[track_caller] +pub fn fill(dest: &mut T) { + dest.fill(&mut rng()) +} + #[cfg(test)] mod test { use super::*; @@ -200,4 +315,11 @@ mod test { (f32, (f64, (f64,))), ) = random(); } + + #[test] + #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] + fn test_range() { + let _n: usize = random_range(42..=43); + let _f: f32 = random_range(42.0..43.0); + } } diff --git a/src/rng.rs b/src/rng.rs index a3657ed45f0..9ac481ed9ce 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -196,7 +196,9 @@ pub trait Rng: RngCore { } /// Return a bool with a probability of `numerator/denominator` of being - /// true. I.e. `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of + /// true. + /// + /// That is, `random_ratio(2, 3)` has chance of 2 in 3, or about 67%, of /// returning true. If `numerator == denominator`, then the returned value /// is guaranteed to be `true`. If `numerator == 0`, then the returned /// value is guaranteed to be `false`. diff --git a/src/seq/iterator.rs b/src/seq/iterator.rs index ad96b3baf79..b10d205676a 100644 --- a/src/seq/iterator.rs +++ b/src/seq/iterator.rs @@ -54,6 +54,15 @@ pub trait IteratorRandom: Iterator + Sized { /// Consider instead using [`IteratorRandom::choose_stable`] to avoid /// [`Iterator`] combinators which only change size hints from affecting the /// results. + /// + /// # Example + /// + /// ``` + /// use rand::seq::IteratorRandom; + /// + /// let words = "Mary had a little lamb".split(' '); + /// println!("{}", words.choose(&mut rand::rng()).unwrap()); + /// ``` fn choose(mut self, rng: &mut R) -> Option where R: Rng + ?Sized, From ad67294a8ac1b549b558c8b5cda8c2a342c652f6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 12 Nov 2024 16:50:35 +0000 Subject: [PATCH 424/443] Zipf: let n have type F (#1518) --- benches/benches/distr.rs | 2 +- rand_distr/CHANGELOG.md | 1 + rand_distr/src/zipf.rs | 31 ++++++++++++++++--------------- rand_distr/tests/cdf.rs | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/benches/benches/distr.rs b/benches/benches/distr.rs index 63d6a1034ee..ec43e7e61cc 100644 --- a/benches/benches/distr.rs +++ b/benches/benches/distr.rs @@ -159,7 +159,7 @@ fn bench(c: &mut Criterion) { g.finish(); let mut g = c.benchmark_group("zipf"); - distr_float!(g, "zipf", f64, Zipf::new(10, 1.5).unwrap()); + distr_float!(g, "zipf", f64, Zipf::new(10.0, 1.5).unwrap()); distr_float!(g, "zeta", f64, Zeta::new(1.5).unwrap()); g.finish(); diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 8e6cfff686b..fc597d47763 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). - Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) +- Change parameter type of `Zipf::new`: `n` is now floating-point (#1518) ### Added - Add plots for `rand_distr` distributions to documentation (#1434) diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 9d98458db08..45a8bf2b5cb 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -35,7 +35,7 @@ use rand::Rng; /// use rand::prelude::*; /// use rand_distr::Zipf; /// -/// let val: f64 = rand::rng().sample(Zipf::new(10, 1.5).unwrap()); +/// let val: f64 = rand::rng().sample(Zipf::new(10.0, 1.5).unwrap()); /// println!("{}", val); /// ``` /// @@ -92,16 +92,17 @@ where /// Construct a new `Zipf` distribution for a set with `n` elements and a /// frequency rank exponent `s`. /// - /// For large `n`, rounding may occur to fit the number into the float type. + /// The parameter `n` is typically integral, however we use type + ///
F: [Float]
in order to permit very large values + /// and since our implementation requires a floating-point type. #[inline] - pub fn new(n: u64, s: F) -> Result, Error> { + pub fn new(n: F, s: F) -> Result, Error> { if !(s >= F::zero()) { return Err(Error::STooSmall); } - if n < 1 { + if n < F::one() { return Err(Error::NTooSmall); } - let n = F::from(n).unwrap(); // This does not fail. let q = if s != F::one() { // Make sure to calculate the division only once. F::one() / (F::one() - s) @@ -173,24 +174,24 @@ mod tests { #[test] #[should_panic] fn zipf_s_too_small() { - Zipf::new(10, -1.).unwrap(); + Zipf::new(10., -1.).unwrap(); } #[test] #[should_panic] fn zipf_n_too_small() { - Zipf::new(0, 1.).unwrap(); + Zipf::new(0., 1.).unwrap(); } #[test] #[should_panic] fn zipf_nan() { - Zipf::new(10, f64::NAN).unwrap(); + Zipf::new(10., f64::NAN).unwrap(); } #[test] fn zipf_sample() { - let d = Zipf::new(10, 0.5).unwrap(); + let d = Zipf::new(10., 0.5).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -200,7 +201,7 @@ mod tests { #[test] fn zipf_sample_s_1() { - let d = Zipf::new(10, 1.).unwrap(); + let d = Zipf::new(10., 1.).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -210,7 +211,7 @@ mod tests { #[test] fn zipf_sample_s_0() { - let d = Zipf::new(10, 0.).unwrap(); + let d = Zipf::new(10., 0.).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -221,7 +222,7 @@ mod tests { #[test] fn zipf_sample_large_n() { - let d = Zipf::new(u64::MAX, 1.5).unwrap(); + let d = Zipf::new(f64::MAX, 1.5).unwrap(); let mut rng = crate::test::rng(2); for _ in 0..1000 { let r = d.sample(&mut rng); @@ -232,12 +233,12 @@ mod tests { #[test] fn zipf_value_stability() { - test_samples(Zipf::new(10, 0.5).unwrap(), 0f32, &[10.0, 2.0, 6.0, 7.0]); - test_samples(Zipf::new(10, 2.0).unwrap(), 0f64, &[1.0, 2.0, 3.0, 2.0]); + test_samples(Zipf::new(10., 0.5).unwrap(), 0f32, &[10.0, 2.0, 6.0, 7.0]); + test_samples(Zipf::new(10., 2.0).unwrap(), 0f64, &[1.0, 2.0, 3.0, 2.0]); } #[test] fn zipf_distributions_can_be_compared() { - assert_eq!(Zipf::new(1, 2.0), Zipf::new(1, 2.0)); + assert_eq!(Zipf::new(1.0, 2.0), Zipf::new(1.0, 2.0)); } } diff --git a/rand_distr/tests/cdf.rs b/rand_distr/tests/cdf.rs index 8eb22740e2b..86e926af00e 100644 --- a/rand_distr/tests/cdf.rs +++ b/rand_distr/tests/cdf.rs @@ -385,7 +385,7 @@ fn zipf() { let parameters = [(1000, 1.0), (500, 2.0), (1000, 0.5)]; for (seed, (n, x)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Zipf::new(n, x).unwrap(); + let dist = rand_distr::Zipf::new(n as f64, x).unwrap(); test_discrete(seed as u64, dist, |k| cdf(k, n, x)); } } From 7fe350c9bac6b11a84687a7e8e33e6fd8e0a8c01 Mon Sep 17 00:00:00 2001 From: Benjamin Lieser Date: Wed, 13 Nov 2024 12:42:45 +0100 Subject: [PATCH 425/443] Hypergeo fix (#1510) --- rand_distr/CHANGELOG.md | 1 + rand_distr/src/hypergeometric.rs | 21 +++++++++++++++++++-- rand_distr/tests/cdf.rs | 2 +- rand_distr/tests/value_stability.rs | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index fc597d47763..81b62a1f28e 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). - Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) +- Fix bug in `Hypergeometric`, this is a Value-breaking change (#1510) - Change parameter type of `Zipf::new`: `n` is now floating-point (#1518) ### Added diff --git a/rand_distr/src/hypergeometric.rs b/rand_distr/src/hypergeometric.rs index c1f1d4ef234..f446357530b 100644 --- a/rand_distr/src/hypergeometric.rs +++ b/rand_distr/src/hypergeometric.rs @@ -131,10 +131,17 @@ fn fraction_of_products_of_factorials(numerator: (u64, u64), denominator: (u64, result } +const LOGSQRT2PI: f64 = 0.91893853320467274178; // log(sqrt(2*pi)) + fn ln_of_factorial(v: f64) -> f64 { // the paper calls for ln(v!), but also wants to pass in fractions, // so we need to use Stirling's approximation to fill in the gaps: - v * v.ln() - v + + // shift v by 3, because Stirling is bad for small values + let v_3 = v + 3.0; + let ln_fac = (v_3 + 0.5) * v_3.ln() - v_3 + LOGSQRT2PI + 1.0 / (12.0 * v_3); + // make the correction for the shift + ln_fac - ((v + 3.0) * (v + 2.0) * (v + 1.0)).ln() } impl Hypergeometric { @@ -359,7 +366,7 @@ impl Distribution for Hypergeometric { } else { for i in (y as u64 + 1)..=(m as u64) { f *= i as f64 * (n2 - k + i) as f64; - f /= (n1 - i) as f64 * (k - i) as f64; + f /= (n1 - i + 1) as f64 * (k - i + 1) as f64; } } @@ -441,6 +448,7 @@ impl Distribution for Hypergeometric { #[cfg(test)] mod test { + use super::*; #[test] @@ -494,4 +502,13 @@ mod test { fn hypergeometric_distributions_can_be_compared() { assert_eq!(Hypergeometric::new(1, 2, 3), Hypergeometric::new(1, 2, 3)); } + + #[test] + fn stirling() { + let test = [0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; + for &v in test.iter() { + let ln_fac = ln_of_factorial(v); + assert!((special::Gamma::ln_gamma(v + 1.0).0 - ln_fac).abs() < 1e-4); + } + } } diff --git a/rand_distr/tests/cdf.rs b/rand_distr/tests/cdf.rs index 86e926af00e..62286860db6 100644 --- a/rand_distr/tests/cdf.rs +++ b/rand_distr/tests/cdf.rs @@ -598,7 +598,7 @@ fn hypergeometric() { (60, 10, 7), (70, 20, 50), (100, 50, 10), - // (100, 50, 49), // Fail case + (100, 50, 49), ]; for (seed, (n, k, n_)) in parameters.into_iter().enumerate() { diff --git a/rand_distr/tests/value_stability.rs b/rand_distr/tests/value_stability.rs index b142741e77c..330119b68f6 100644 --- a/rand_distr/tests/value_stability.rs +++ b/rand_distr/tests/value_stability.rs @@ -105,7 +105,7 @@ fn hypergeometric_stability() { test_samples( 7221, Hypergeometric::new(100, 50, 50).unwrap(), - &[23, 27, 26, 27, 22, 24, 31, 22], + &[23, 27, 26, 27, 22, 25, 31, 25], ); // Algorithm H2PE } From e85c9232c621bb26a3d98ed9598129f15e6d1808 Mon Sep 17 00:00:00 2001 From: Benjamin Lieser Date: Mon, 18 Nov 2024 11:14:37 +0100 Subject: [PATCH 426/443] Move KS tests (#1525) Adds new non-publishing distr_test crate --- .github/workflows/distr_test.yml | 24 + .github/workflows/test.yml | 11 + Cargo.toml | 2 +- distr_test/Cargo.toml | 15 + distr_test/tests/cdf.rs | 454 ++++++++++++++ distr_test/tests/ks/mod.rs | 137 +++++ distr_test/tests/skew_normal.rs | 266 ++++++++ distr_test/tests/zeta.rs | 56 ++ rand_distr/tests/cdf.rs | 883 --------------------------- rand_distr/tests/common/mod.rs | 1 - rand_distr/tests/common/spec_func.rs | 155 ----- rand_distr/tests/pdf.rs | 179 ------ rand_distr/tests/sparkline.rs | 126 ---- rand_distr/tests/uniformity.rs | 65 -- 14 files changed, 964 insertions(+), 1410 deletions(-) create mode 100644 .github/workflows/distr_test.yml create mode 100644 distr_test/Cargo.toml create mode 100644 distr_test/tests/cdf.rs create mode 100644 distr_test/tests/ks/mod.rs create mode 100644 distr_test/tests/skew_normal.rs create mode 100644 distr_test/tests/zeta.rs delete mode 100644 rand_distr/tests/cdf.rs delete mode 100644 rand_distr/tests/common/mod.rs delete mode 100644 rand_distr/tests/common/spec_func.rs delete mode 100644 rand_distr/tests/pdf.rs delete mode 100644 rand_distr/tests/sparkline.rs delete mode 100644 rand_distr/tests/uniformity.rs diff --git a/.github/workflows/distr_test.yml b/.github/workflows/distr_test.yml new file mode 100644 index 00000000000..34d632770af --- /dev/null +++ b/.github/workflows/distr_test.yml @@ -0,0 +1,24 @@ +name: distr_test + +on: + pull_request: + paths: + - ".github/workflows/distr_test.yml" + - "distr_test/**" + +jobs: + distr_test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./distr_test + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: clippy, rustfmt + - name: Rustfmt + run: cargo fmt -- --check + - name: Clippy + run: cargo clippy --all-targets -- -D warnings diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fadd73f4aa2..1108a9cab2a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -111,6 +111,17 @@ jobs: - name: Test rand_chacha run: cargo test --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml --features=serde + test-ks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install toolchain + uses: dtolnay/rust-toolchain@nightly + with: + target: x86_64-unknown-linux-gnu + - name: Test Komogorov Smirnov + run: cargo test --manifest-path distr_test/Cargo.toml --release + test-cross: runs-on: ${{ matrix.os }} strategy: diff --git a/Cargo.toml b/Cargo.toml index 1f2eccdc638..0b22e9606b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ members = [ "rand_chacha", "rand_pcg", ] -exclude = ["benches"] +exclude = ["benches", "distr_test"] [dependencies] rand_core = { path = "rand_core", version = "=0.9.0-alpha.1", default-features = false } diff --git a/distr_test/Cargo.toml b/distr_test/Cargo.toml new file mode 100644 index 00000000000..7f37853b3c1 --- /dev/null +++ b/distr_test/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "distr_test" +version = "0.1.0" +edition = "2021" +publish = false + +[dev-dependencies] +rand_distr = { path = "../rand_distr", version = "=0.5.0-alpha.1", default-features = false } +rand = { path = "..", version = "=0.9.0-alpha.1", features = ["small_rng"] } +num-traits = "0.2.19" +# Special functions for testing distributions +special = "0.11.0" +spfunc = "0.1.0" +# Cdf implementation +statrs = "0.17.1" diff --git a/distr_test/tests/cdf.rs b/distr_test/tests/cdf.rs new file mode 100644 index 00000000000..f417c630ae2 --- /dev/null +++ b/distr_test/tests/cdf.rs @@ -0,0 +1,454 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::f64; + +use special::{Beta, Gamma, Primitive}; +use statrs::distribution::ContinuousCDF; +use statrs::distribution::DiscreteCDF; + +mod ks; +use ks::test_continuous; +use ks::test_discrete; + +#[test] +fn normal() { + let parameters = [ + (0.0, 1.0), + (0.0, 0.1), + (1.0, 10.0), + (1.0, 100.0), + (-1.0, 0.00001), + (-1.0, 0.0000001), + ]; + + for (seed, (mean, std_dev)) in parameters.into_iter().enumerate() { + test_continuous( + seed as u64, + rand_distr::Normal::new(mean, std_dev).unwrap(), + |x| { + statrs::distribution::Normal::new(mean, std_dev) + .unwrap() + .cdf(x) + }, + ); + } +} + +#[test] +fn cauchy() { + let parameters = [ + (0.0, 1.0), + (0.0, 0.1), + (1.0, 10.0), + (1.0, 100.0), + (-1.0, 0.00001), + (-1.0, 0.0000001), + ]; + + for (seed, (median, scale)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Cauchy::new(median, scale).unwrap(); + test_continuous(seed as u64, dist, |x| { + statrs::distribution::Cauchy::new(median, scale) + .unwrap() + .cdf(x) + }); + } +} + +#[test] +fn uniform() { + fn cdf(x: f64, a: f64, b: f64) -> f64 { + if x < a { + 0.0 + } else if x < b { + (x - a) / (b - a) + } else { + 1.0 + } + } + + let parameters = [(0.0, 1.0), (-1.0, 1.0), (0.0, 100.0), (-100.0, 100.0)]; + + for (seed, (a, b)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Uniform::new(a, b).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, a, b)); + } +} + +#[test] +fn log_normal() { + let parameters = [ + (0.0, 1.0), + (0.0, 0.1), + (0.5, 0.7), + (1.0, 10.0), + (1.0, 100.0), + ]; + + for (seed, (mean, std_dev)) in parameters.into_iter().enumerate() { + let dist = rand_distr::LogNormal::new(mean, std_dev).unwrap(); + test_continuous(seed as u64, dist, |x| { + statrs::distribution::LogNormal::new(mean, std_dev) + .unwrap() + .cdf(x) + }); + } +} + +#[test] +fn pareto() { + let parameters = [ + (1.0, 1.0), + (1.0, 0.1), + (1.0, 10.0), + (1.0, 100.0), + (0.1, 1.0), + (10.0, 1.0), + (100.0, 1.0), + ]; + + for (seed, (scale, alpha)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Pareto::new(scale, alpha).unwrap(); + test_continuous(seed as u64, dist, |x| { + statrs::distribution::Pareto::new(scale, alpha) + .unwrap() + .cdf(x) + }); + } +} + +#[test] +fn exp() { + fn cdf(x: f64, lambda: f64) -> f64 { + 1.0 - (-lambda * x).exp() + } + + let parameters = [0.5, 1.0, 7.5, 32.0, 100.0]; + + for (seed, lambda) in parameters.into_iter().enumerate() { + let dist = rand_distr::Exp::new(lambda).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, lambda)); + } +} + +#[test] +fn weibull() { + fn cdf(x: f64, lambda: f64, k: f64) -> f64 { + if x < 0.0 { + return 0.0; + } + + 1.0 - (-(x / lambda).powf(k)).exp() + } + + let parameters = [ + (0.5, 1.0), + (1.0, 1.0), + (10.0, 0.1), + (0.1, 10.0), + (15.0, 20.0), + (1000.0, 0.01), + ]; + + for (seed, (lambda, k)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Weibull::new(lambda, k).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, lambda, k)); + } +} + +#[test] +fn gumbel() { + fn cdf(x: f64, mu: f64, beta: f64) -> f64 { + (-(-(x - mu) / beta).exp()).exp() + } + + let parameters = [ + (0.0, 1.0), + (1.0, 2.0), + (-1.0, 0.5), + (10.0, 0.1), + (100.0, 0.0001), + ]; + + for (seed, (mu, beta)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Gumbel::new(mu, beta).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, mu, beta)); + } +} + +#[test] +fn frechet() { + fn cdf(x: f64, alpha: f64, s: f64, m: f64) -> f64 { + if x < m { + return 0.0; + } + + (-((x - m) / s).powf(-alpha)).exp() + } + + let parameters = [ + (0.5, 2.0, 1.0), + (1.0, 1.0, 1.0), + (10.0, 0.1, 1.0), + (100.0, 0.0001, 1.0), + (0.9999, 2.0, 1.0), + ]; + + for (seed, (alpha, s, m)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Frechet::new(m, s, alpha).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, alpha, s, m)); + } +} + +#[test] +fn gamma() { + fn cdf(x: f64, shape: f64, scale: f64) -> f64 { + if x < 0.0 { + return 0.0; + } + + (x / scale).inc_gamma(shape) + } + + let parameters = [ + (0.5, 2.0), + (1.0, 1.0), + (10.0, 0.1), + (100.0, 0.0001), + (0.9999, 2.0), + ]; + + for (seed, (shape, scale)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Gamma::new(shape, scale).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, shape, scale)); + } +} + +#[test] +fn chi_squared() { + fn cdf(x: f64, k: f64) -> f64 { + if x < 0.0 { + return 0.0; + } + + (x / 2.0).inc_gamma(k / 2.0) + } + + let parameters = [0.1, 1.0, 2.0, 10.0, 100.0, 1000.0]; + + for (seed, k) in parameters.into_iter().enumerate() { + let dist = rand_distr::ChiSquared::new(k).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, k)); + } +} +#[test] +fn studend_t() { + fn cdf(x: f64, df: f64) -> f64 { + let h = df / (df + x.powi(2)); + let ib = 0.5 * h.inc_beta(df / 2.0, 0.5, 0.5.ln_beta(df / 2.0)); + if x < 0.0 { + ib + } else { + 1.0 - ib + } + } + + let parameters = [1.0, 10.0, 50.0]; + + for (seed, df) in parameters.into_iter().enumerate() { + let dist = rand_distr::StudentT::new(df).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, df)); + } +} + +#[test] +fn fisher_f() { + fn cdf(x: f64, m: f64, n: f64) -> f64 { + if (m == 1.0 && x <= 0.0) || x < 0.0 { + 0.0 + } else { + let k = m * x / (m * x + n); + let d1 = m / 2.0; + let d2 = n / 2.0; + k.inc_beta(d1, d2, d1.ln_beta(d2)) + } + } + + let parameters = [(1.0, 1.0), (1.0, 2.0), (2.0, 1.0), (50.0, 1.0)]; + + for (seed, (m, n)) in parameters.into_iter().enumerate() { + let dist = rand_distr::FisherF::new(m, n).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, m, n)); + } +} + +#[test] +fn beta() { + fn cdf(x: f64, alpha: f64, beta: f64) -> f64 { + if x < 0.0 { + return 0.0; + } + if x > 1.0 { + return 1.0; + } + let ln_beta_ab = alpha.ln_beta(beta); + x.inc_beta(alpha, beta, ln_beta_ab) + } + + let parameters = [(0.5, 0.5), (2.0, 3.5), (10.0, 1.0), (100.0, 50.0)]; + + for (seed, (alpha, beta)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Beta::new(alpha, beta).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, alpha, beta)); + } +} + +#[test] +fn triangular() { + fn cdf(x: f64, a: f64, b: f64, c: f64) -> f64 { + if x <= a { + 0.0 + } else if a < x && x <= c { + (x - a).powi(2) / ((b - a) * (c - a)) + } else if c < x && x < b { + 1.0 - (b - x).powi(2) / ((b - a) * (b - c)) + } else { + 1.0 + } + } + + let parameters = [ + (0.0, 1.0, 0.0001), + (0.0, 1.0, 0.9999), + (0.0, 1.0, 0.5), + (0.0, 100.0, 50.0), + (-100.0, 100.0, 0.0), + ]; + + for (seed, (a, b, c)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Triangular::new(a, b, c).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, a, b, c)); + } +} + +fn binomial_cdf(k: i64, p: f64, n: u64) -> f64 { + if k < 0 { + return 0.0; + } + let k = k as u64; + if k >= n { + return 1.0; + } + + let a = (n - k) as f64; + let b = k as f64 + 1.0; + + let q = 1.0 - p; + + let ln_beta_ab = a.ln_beta(b); + + q.inc_beta(a, b, ln_beta_ab) +} + +#[test] +fn binomial() { + let parameters = [ + (0.5, 10), + (0.5, 100), + (0.1, 10), + (0.0000001, 1000000), + (0.0000001, 10), + (0.9999, 2), + ]; + + for (seed, (p, n)) in parameters.into_iter().enumerate() { + test_discrete(seed as u64, rand_distr::Binomial::new(n, p).unwrap(), |k| { + binomial_cdf(k, p, n) + }); + } +} + +#[test] +fn geometric() { + fn cdf(k: i64, p: f64) -> f64 { + if k < 0 { + 0.0 + } else { + 1.0 - (1.0 - p).powi(1 + k as i32) + } + } + + let parameters = [0.3, 0.5, 0.7, 0.0000001, 0.9999]; + + for (seed, p) in parameters.into_iter().enumerate() { + let dist = rand_distr::Geometric::new(p).unwrap(); + test_discrete(seed as u64, dist, |k| cdf(k, p)); + } +} + +#[test] +fn hypergeometric() { + fn cdf(x: i64, n: u64, k: u64, n_: u64) -> f64 { + let min = if n_ + k > n { n_ + k - n } else { 0 }; + let max = k.min(n_); + if x < min as i64 { + return 0.0; + } else if x >= max as i64 { + return 1.0; + } + + (min..x as u64 + 1).fold(0.0, |acc, k_| { + acc + (ln_binomial(k, k_) + ln_binomial(n - k, n_ - k_) - ln_binomial(n, n_)).exp() + }) + } + + let parameters = [ + (15, 13, 10), + (25, 15, 5), + (60, 10, 7), + (70, 20, 50), + (100, 50, 10), + (100, 50, 49), + ]; + + for (seed, (n, k, n_)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Hypergeometric::new(n, k, n_).unwrap(); + test_discrete(seed as u64, dist, |x| cdf(x, n, k, n_)); + } +} + +#[test] +fn poisson() { + use rand_distr::Poisson; + let parameters = [ + 0.1, 1.0, 7.5, + 45.0, // 1e9, passed case but too slow + // 1.844E+19, // fail case + ]; + + for (seed, lambda) in parameters.into_iter().enumerate() { + let dist = Poisson::new(lambda).unwrap(); + let analytic = statrs::distribution::Poisson::new(lambda).unwrap(); + test_discrete::, _>(seed as u64, dist, |k| { + if k < 0 { + 0.0 + } else { + analytic.cdf(k as u64) + } + }); + } +} + +fn ln_factorial(n: u64) -> f64 { + (n as f64 + 1.0).lgamma().0 +} + +fn ln_binomial(n: u64, k: u64) -> f64 { + ln_factorial(n) - ln_factorial(k) - ln_factorial(n - k) +} diff --git a/distr_test/tests/ks/mod.rs b/distr_test/tests/ks/mod.rs new file mode 100644 index 00000000000..ab94db6e1f4 --- /dev/null +++ b/distr_test/tests/ks/mod.rs @@ -0,0 +1,137 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// [1] Nonparametric Goodness-of-Fit Tests for Discrete Null Distributions +// by Taylor B. Arnold and John W. Emerson +// http://www.stat.yale.edu/~jay/EmersonMaterials/DiscreteGOF.pdf + +#![allow(dead_code)] + +use num_traits::AsPrimitive; +use rand::SeedableRng; +use rand_distr::Distribution; + +/// Empirical Cumulative Distribution Function (ECDF) +struct Ecdf { + sorted_samples: Vec, +} + +impl Ecdf { + fn new(mut samples: Vec) -> Self { + samples.sort_by(|a, b| a.partial_cmp(b).unwrap()); + Self { + sorted_samples: samples, + } + } + + /// Returns the step points of the ECDF + /// The ECDF is a step function that increases by 1/n at each sample point + /// The function is continuous from the right, so we give the bigger value at the step points + /// First point is (-inf, 0.0), last point is (max(samples), 1.0) + fn step_points(&self) -> Vec<(f64, f64)> { + let mut points = Vec::with_capacity(self.sorted_samples.len() + 1); + let mut last = f64::NEG_INFINITY; + let mut count = 0; + let n = self.sorted_samples.len() as f64; + for &x in &self.sorted_samples { + if x != last { + points.push((last, count as f64 / n)); + last = x; + } + count += 1; + } + points.push((last, count as f64 / n)); + points + } +} + +fn kolmogorov_smirnov_statistic_continuous(ecdf: Ecdf, cdf: impl Fn(f64) -> f64) -> f64 { + // We implement equation (3) from [1] + + let mut max_diff: f64 = 0.; + + let step_points = ecdf.step_points(); // x_i in the paper + for i in 1..step_points.len() { + let (x_i, f_i) = step_points[i]; + let (_, f_i_1) = step_points[i - 1]; + let cdf_i = cdf(x_i); + let max_1 = (cdf_i - f_i).abs(); + let max_2 = (cdf_i - f_i_1).abs(); + + max_diff = max_diff.max(max_1).max(max_2); + } + max_diff +} + +fn kolmogorov_smirnov_statistic_discrete(ecdf: Ecdf, cdf: impl Fn(i64) -> f64) -> f64 { + // We implement equation (4) from [1] + + let mut max_diff: f64 = 0.; + + let step_points = ecdf.step_points(); // x_i in the paper + for i in 1..step_points.len() { + let (x_i, f_i) = step_points[i]; + let (_, f_i_1) = step_points[i - 1]; + let max_1 = (cdf(x_i as i64) - f_i).abs(); + let max_2 = (cdf(x_i as i64 - 1) - f_i_1).abs(); // -1 is the same as -epsilon, because we have integer support + + max_diff = max_diff.max(max_1).max(max_2); + } + max_diff +} + +const SAMPLE_SIZE: u64 = 1_000_000; + +fn critical_value() -> f64 { + // If the sampler is correct, we expect less than 0.001 false positives (alpha = 0.001). + // Passing this does not prove that the sampler is correct but is a good indication. + 1.95 / (SAMPLE_SIZE as f64).sqrt() +} + +fn sample_ecdf(seed: u64, dist: impl Distribution) -> Ecdf +where + T: AsPrimitive, +{ + let mut rng = rand::rngs::SmallRng::seed_from_u64(seed); + let samples = (0..SAMPLE_SIZE) + .map(|_| dist.sample(&mut rng).as_()) + .collect(); + Ecdf::new(samples) +} + +/// Tests a distribution against an analytical CDF. +/// The CDF has to be continuous. +pub fn test_continuous(seed: u64, dist: impl Distribution, cdf: impl Fn(f64) -> f64) { + let ecdf = sample_ecdf(seed, dist); + let ks_statistic = kolmogorov_smirnov_statistic_continuous(ecdf, cdf); + + let critical_value = critical_value(); + + println!("KS statistic: {}", ks_statistic); + println!("Critical value: {}", critical_value); + assert!(ks_statistic < critical_value); +} + +/// Tests a distribution over integers against an analytical CDF. +/// The analytical CDF must not have jump points which are not integers. +pub fn test_discrete(seed: u64, dist: D, cdf: F) +where + I: AsPrimitive, + D: Distribution, + F: Fn(i64) -> f64, +{ + let ecdf = sample_ecdf(seed, dist); + let ks_statistic = kolmogorov_smirnov_statistic_discrete(ecdf, cdf); + + // This critical value is bigger than it could be for discrete distributions, but because of large sample sizes this should not matter too much + let critical_value = critical_value(); + + println!("KS statistic: {}", ks_statistic); + println!("Critical value: {}", critical_value); + assert!(ks_statistic < critical_value); +} diff --git a/distr_test/tests/skew_normal.rs b/distr_test/tests/skew_normal.rs new file mode 100644 index 00000000000..0e6b7b3a028 --- /dev/null +++ b/distr_test/tests/skew_normal.rs @@ -0,0 +1,266 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod ks; +use ks::test_continuous; +use special::Primitive; + +#[test] +fn skew_normal() { + fn cdf(x: f64, location: f64, scale: f64, shape: f64) -> f64 { + let norm = (x - location) / scale; + phi(norm) - 2.0 * owen_t(norm, shape) + } + + let parameters = [(0.0, 1.0, 5.0), (1.0, 10.0, -5.0), (-1.0, 0.00001, 0.0)]; + + for (seed, (location, scale, shape)) in parameters.into_iter().enumerate() { + let dist = rand_distr::SkewNormal::new(location, scale, shape).unwrap(); + test_continuous(seed as u64, dist, |x| cdf(x, location, scale, shape)); + } +} + +/// [1] Patefield, M. (2000). Fast and Accurate Calculation of Owen’s T Function. +/// Journal of Statistical Software, 5(5), 1–25. +/// https://doi.org/10.18637/jss.v005.i05 +/// +/// This function is ported to Rust from the Fortran code provided in the paper +fn owen_t(h: f64, a: f64) -> f64 { + let absh = h.abs(); + let absa = a.abs(); + let ah = absa * absh; + + let mut t; + if absa <= 1.0 { + t = tf(absh, absa, ah); + } else if absh <= 0.67 { + t = 0.25 - znorm1(absh) * znorm1(ah) - tf(ah, 1.0 / absa, absh); + } else { + let normh = znorm2(absh); + let normah = znorm2(ah); + t = 0.5 * (normh + normah) - normh * normah - tf(ah, 1.0 / absa, absh); + } + + if a < 0.0 { + t = -t; + } + + fn tf(h: f64, a: f64, ah: f64) -> f64 { + let rtwopi = 0.159_154_943_091_895_35; + let rrtpi = 0.398_942_280_401_432_7; + + let c2 = [ + 0.999_999_999_999_999_9, + -0.999_999_999_999_888, + 0.999_999_999_982_907_5, + -0.999_999_998_962_825, + 0.999_999_966_604_593_7, + -0.999_999_339_862_724_7, + 0.999_991_256_111_369_6, + -0.999_917_776_244_633_8, + 0.999_428_355_558_701_4, + -0.996_973_117_207_23, + 0.987_514_480_372_753, + -0.959_158_579_805_728_8, + 0.892_463_055_110_067_1, + -0.768_934_259_904_64, + 0.588_935_284_684_846_9, + -0.383_803_451_604_402_55, + 0.203_176_017_010_453, + -8.281_363_160_700_499e-2, + 2.416_798_473_575_957_8e-2, + -4.467_656_666_397_183e-3, + 3.914_116_940_237_383_6e-4, + ]; + + let pts = [ + 3.508_203_967_645_171_6e-3, + 3.127_904_233_803_075_6e-2, + 8.526_682_628_321_945e-2, + 0.162_450_717_308_122_77, + 0.258_511_960_491_254_36, + 0.368_075_538_406_975_3, + 0.485_010_929_056_047, + 0.602_775_141_526_185_7, + 0.714_778_842_177_532_3, + 0.814_755_109_887_601, + 0.897_110_297_559_489_7, + 0.957_238_080_859_442_6, + 0.991_788_329_746_297, + ]; + + let wts = [ + 1.883_143_811_532_350_3e-2, + 1.856_708_624_397_765e-2, + 1.804_209_346_122_338_5e-2, + 1.726_382_960_639_875_2e-2, + 1.624_321_997_598_985_8e-2, + 1.499_459_203_411_670_5e-2, + 1.353_547_446_966_209e-2, + 1.188_635_160_582_016_5e-2, + 1.007_037_724_277_743_2e-2, + 8.113_054_574_229_958e-3, + 6.041_900_952_847_024e-3, + 3.886_221_701_074_205_7e-3, + 1.679_303_108_454_609e-3, + ]; + + let hrange = [ + 0.02, 0.06, 0.09, 0.125, 0.26, 0.4, 0.6, 1.6, 1.7, 2.33, 2.4, 3.36, 3.4, 4.8, + ]; + let arange = [0.025, 0.09, 0.15, 0.36, 0.5, 0.9, 0.99999]; + + let select = [ + [1, 1, 2, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 9], + [1, 2, 2, 3, 3, 5, 5, 14, 14, 15, 15, 16, 16, 16, 9], + [2, 2, 3, 3, 3, 5, 5, 15, 15, 15, 15, 16, 16, 16, 10], + [2, 2, 3, 5, 5, 5, 5, 7, 7, 16, 16, 16, 16, 16, 10], + [2, 3, 3, 5, 5, 6, 6, 8, 8, 17, 17, 17, 12, 12, 11], + [2, 3, 5, 5, 5, 6, 6, 8, 8, 17, 17, 17, 12, 12, 12], + [2, 3, 4, 4, 6, 6, 8, 8, 17, 17, 17, 17, 17, 12, 12], + [2, 3, 4, 4, 6, 6, 18, 18, 18, 18, 17, 17, 17, 12, 12], + ]; + + let ihint = hrange.iter().position(|&r| h < r).unwrap_or(14); + + let iaint = arange.iter().position(|&r| a < r).unwrap_or(7); + + let icode = select[iaint][ihint]; + let m = [ + 2, 3, 4, 5, 7, 10, 12, 18, 10, 20, 30, 20, 4, 7, 8, 20, 13, 0, + ][icode - 1]; + let method = [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5, 6][icode - 1]; + + match method { + 1 => { + let hs = -0.5 * h * h; + let dhs = hs.exp(); + let as_ = a * a; + let mut j = 1; + let mut jj = 1; + let mut aj = rtwopi * a; + let mut tf = rtwopi * a.atan(); + let mut dj = dhs - 1.0; + let mut gj = hs * dhs; + loop { + tf += dj * aj / (jj as f64); + if j >= m { + return tf; + } + j += 1; + jj += 2; + aj *= as_; + dj = gj - dj; + gj *= hs / (j as f64); + } + } + 2 => { + let maxii = m + m + 1; + let mut ii = 1; + let mut tf = 0.0; + let hs = h * h; + let as_ = -a * a; + let mut vi = rrtpi * a * (-0.5 * ah * ah).exp(); + let mut z = znorm1(ah) / h; + let y = 1.0 / hs; + loop { + tf += z; + if ii >= maxii { + tf *= rrtpi * (-0.5 * hs).exp(); + return tf; + } + z = y * (vi - (ii as f64) * z); + vi *= as_; + ii += 2; + } + } + 3 => { + let mut i = 1; + let mut ii = 1; + let mut tf = 0.0; + let hs = h * h; + let as_ = a * a; + let mut vi = rrtpi * a * (-0.5 * ah * ah).exp(); + let mut zi = znorm1(ah) / h; + let y = 1.0 / hs; + loop { + tf += zi * c2[i - 1]; + if i > m { + tf *= rrtpi * (-0.5 * hs).exp(); + return tf; + } + zi = y * ((ii as f64) * zi - vi); + vi *= as_; + i += 1; + ii += 2; + } + } + 4 => { + let maxii = m + m + 1; + let mut ii = 1; + let mut tf = 0.0; + let hs = h * h; + let as_ = -a * a; + let mut ai = rtwopi * a * (-0.5 * hs * (1.0 - as_)).exp(); + let mut yi = 1.0; + loop { + tf += ai * yi; + if ii >= maxii { + return tf; + } + ii += 2; + yi = (1.0 - hs * yi) / (ii as f64); + ai *= as_; + } + } + 5 => { + let mut tf = 0.0; + let as_ = a * a; + let hs = -0.5 * h * h; + for i in 0..m { + let r = 1.0 + as_ * pts[i]; + tf += wts[i] * (hs * r).exp() / r; + } + tf *= a; + tf + } + 6 => { + let normh = znorm2(h); + let mut tf = 0.5 * normh * (1.0 - normh); + let y = 1.0 - a; + let r = (y / (1.0 + a)).atan(); + if r != 0.0 { + tf -= rtwopi * r * (-0.5 * y * h * h / r).exp(); + } + tf + } + _ => 0.0, + } + } + + // P(0 ≤ Z ≤ x) + fn znorm1(x: f64) -> f64 { + phi(x) - 0.5 + } + + // P(x ≤ Z < ∞) + fn znorm2(x: f64) -> f64 { + 1.0 - phi(x) + } + + t +} + +fn normal_cdf(x: f64, mean: f64, std_dev: f64) -> f64 { + 0.5 * ((mean - x) / (std_dev * core::f64::consts::SQRT_2)).erfc() +} + +/// standard normal cdf +fn phi(x: f64) -> f64 { + normal_cdf(x, 0.0, 1.0) +} diff --git a/distr_test/tests/zeta.rs b/distr_test/tests/zeta.rs new file mode 100644 index 00000000000..6e5ab1f594e --- /dev/null +++ b/distr_test/tests/zeta.rs @@ -0,0 +1,56 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod ks; +use ks::test_discrete; + +#[test] +fn zeta() { + fn cdf(k: i64, s: f64) -> f64 { + use spfunc::zeta::zeta as zeta_func; + if k < 1 { + return 0.0; + } + + gen_harmonic(k as u64, s) / zeta_func(s) + } + + let parameters = [2.0, 3.7, 5.0, 100.0]; + + for (seed, s) in parameters.into_iter().enumerate() { + let dist = rand_distr::Zeta::new(s).unwrap(); + test_discrete(seed as u64, dist, |k| cdf(k, s)); + } +} + +#[test] +fn zipf() { + fn cdf(k: i64, n: u64, s: f64) -> f64 { + if k < 1 { + return 0.0; + } + if k > n as i64 { + return 1.0; + } + gen_harmonic(k as u64, s) / gen_harmonic(n, s) + } + + let parameters = [(1000, 1.0), (500, 2.0), (1000, 0.5)]; + + for (seed, (n, x)) in parameters.into_iter().enumerate() { + let dist = rand_distr::Zipf::new(n as f64, x).unwrap(); + test_discrete(seed as u64, dist, |k| cdf(k, n, x)); + } +} + +fn gen_harmonic(n: u64, m: f64) -> f64 { + match n { + 0 => 1.0, + _ => (0..n).fold(0.0, |acc, x| acc + (x as f64 + 1.0).powf(-m)), + } +} diff --git a/rand_distr/tests/cdf.rs b/rand_distr/tests/cdf.rs deleted file mode 100644 index 62286860db6..00000000000 --- a/rand_distr/tests/cdf.rs +++ /dev/null @@ -1,883 +0,0 @@ -// Copyright 2021 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -mod common; -use core::f64; - -use num_traits::AsPrimitive; -use rand::SeedableRng; -use rand_distr::{Distribution, Normal}; -use special::{Beta, Gamma, Primitive}; - -// [1] Nonparametric Goodness-of-Fit Tests for Discrete Null Distributions -// by Taylor B. Arnold and John W. Emerson -// http://www.stat.yale.edu/~jay/EmersonMaterials/DiscreteGOF.pdf - -/// Empirical Cumulative Distribution Function (ECDF) -struct Ecdf { - sorted_samples: Vec, -} - -impl Ecdf { - fn new(mut samples: Vec) -> Self { - samples.sort_by(|a, b| a.partial_cmp(b).unwrap()); - Self { - sorted_samples: samples, - } - } - - /// Returns the step points of the ECDF - /// The ECDF is a step function that increases by 1/n at each sample point - /// The function is continuous from the right, so we give the bigger value at the step points - /// First point is (-inf, 0.0), last point is (max(samples), 1.0) - fn step_points(&self) -> Vec<(f64, f64)> { - let mut points = Vec::with_capacity(self.sorted_samples.len() + 1); - let mut last = f64::NEG_INFINITY; - let mut count = 0; - let n = self.sorted_samples.len() as f64; - for &x in &self.sorted_samples { - if x != last { - points.push((last, count as f64 / n)); - last = x; - } - count += 1; - } - points.push((last, count as f64 / n)); - points - } -} - -fn kolmogorov_smirnov_statistic_continuous(ecdf: Ecdf, cdf: impl Fn(f64) -> f64) -> f64 { - // We implement equation (3) from [1] - - let mut max_diff: f64 = 0.; - - let step_points = ecdf.step_points(); // x_i in the paper - for i in 1..step_points.len() { - let (x_i, f_i) = step_points[i]; - let (_, f_i_1) = step_points[i - 1]; - let cdf_i = cdf(x_i); - let max_1 = (cdf_i - f_i).abs(); - let max_2 = (cdf_i - f_i_1).abs(); - - max_diff = max_diff.max(max_1).max(max_2); - } - max_diff -} - -fn kolmogorov_smirnov_statistic_discrete(ecdf: Ecdf, cdf: impl Fn(i64) -> f64) -> f64 { - // We implement equation (4) from [1] - - let mut max_diff: f64 = 0.; - - let step_points = ecdf.step_points(); // x_i in the paper - for i in 1..step_points.len() { - let (x_i, f_i) = step_points[i]; - let (_, f_i_1) = step_points[i - 1]; - let max_1 = (cdf(x_i as i64) - f_i).abs(); - let max_2 = (cdf(x_i as i64 - 1) - f_i_1).abs(); // -1 is the same as -epsilon, because we have integer support - - max_diff = max_diff.max(max_1).max(max_2); - } - max_diff -} - -const SAMPLE_SIZE: u64 = 1_000_000; - -fn critical_value() -> f64 { - // If the sampler is correct, we expect less than 0.001 false positives (alpha = 0.001). - // Passing this does not prove that the sampler is correct but is a good indication. - 1.95 / (SAMPLE_SIZE as f64).sqrt() -} - -fn sample_ecdf(seed: u64, dist: impl Distribution) -> Ecdf -where - T: AsPrimitive, -{ - let mut rng = rand::rngs::SmallRng::seed_from_u64(seed); - let samples = (0..SAMPLE_SIZE) - .map(|_| dist.sample(&mut rng).as_()) - .collect(); - Ecdf::new(samples) -} - -/// Tests a distribution against an analytical CDF. -/// The CDF has to be continuous. -pub fn test_continuous(seed: u64, dist: impl Distribution, cdf: impl Fn(f64) -> f64) { - let ecdf = sample_ecdf(seed, dist); - let ks_statistic = kolmogorov_smirnov_statistic_continuous(ecdf, cdf); - - let critical_value = critical_value(); - - println!("KS statistic: {}", ks_statistic); - println!("Critical value: {}", critical_value); - assert!(ks_statistic < critical_value); -} - -/// Tests a distribution over integers against an analytical CDF. -/// The analytical CDF must not have jump points which are not integers. -pub fn test_discrete(seed: u64, dist: D, cdf: F) -where - I: AsPrimitive, - D: Distribution, - F: Fn(i64) -> f64, -{ - let ecdf = sample_ecdf(seed, dist); - let ks_statistic = kolmogorov_smirnov_statistic_discrete(ecdf, cdf); - - // This critical value is bigger than it could be for discrete distributions, but because of large sample sizes this should not matter too much - let critical_value = critical_value(); - - println!("KS statistic: {}", ks_statistic); - println!("Critical value: {}", critical_value); - assert!(ks_statistic < critical_value); -} - -fn normal_cdf(x: f64, mean: f64, std_dev: f64) -> f64 { - 0.5 * ((mean - x) / (std_dev * f64::consts::SQRT_2)).erfc() -} - -#[test] -fn normal() { - let parameters = [ - (0.0, 1.0), - (0.0, 0.1), - (1.0, 10.0), - (1.0, 100.0), - (-1.0, 0.00001), - (-1.0, 0.0000001), - ]; - - for (seed, (mean, std_dev)) in parameters.into_iter().enumerate() { - test_continuous(seed as u64, Normal::new(mean, std_dev).unwrap(), |x| { - normal_cdf(x, mean, std_dev) - }); - } -} - -#[test] -fn skew_normal() { - fn cdf(x: f64, location: f64, scale: f64, shape: f64) -> f64 { - let norm = (x - location) / scale; - phi(norm) - 2.0 * owen_t(norm, shape) - } - - let parameters = [(0.0, 1.0, 5.0), (1.0, 10.0, -5.0), (-1.0, 0.00001, 0.0)]; - - for (seed, (location, scale, shape)) in parameters.into_iter().enumerate() { - let dist = rand_distr::SkewNormal::new(location, scale, shape).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, location, scale, shape)); - } -} - -#[test] -fn cauchy() { - fn cdf(x: f64, median: f64, scale: f64) -> f64 { - (1.0 / f64::consts::PI) * ((x - median) / scale).atan() + 0.5 - } - - let parameters = [ - (0.0, 1.0), - (0.0, 0.1), - (1.0, 10.0), - (1.0, 100.0), - (-1.0, 0.00001), - (-1.0, 0.0000001), - ]; - - for (seed, (median, scale)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Cauchy::new(median, scale).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, median, scale)); - } -} - -#[test] -fn uniform() { - fn cdf(x: f64, a: f64, b: f64) -> f64 { - if x < a { - 0.0 - } else if x < b { - (x - a) / (b - a) - } else { - 1.0 - } - } - - let parameters = [(0.0, 1.0), (-1.0, 1.0), (0.0, 100.0), (-100.0, 100.0)]; - - for (seed, (a, b)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Uniform::new(a, b).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, a, b)); - } -} - -#[test] -fn log_normal() { - fn cdf(x: f64, mean: f64, std_dev: f64) -> f64 { - if x <= 0.0 { - 0.0 - } else if x.is_infinite() { - 1.0 - } else { - 0.5 * ((mean - x.ln()) / (std_dev * f64::consts::SQRT_2)).erfc() - } - } - - let parameters = [ - (0.0, 1.0), - (0.0, 0.1), - (0.5, 0.7), - (1.0, 10.0), - (1.0, 100.0), - ]; - - for (seed, (mean, std_dev)) in parameters.into_iter().enumerate() { - let dist = rand_distr::LogNormal::new(mean, std_dev).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, mean, std_dev)); - } -} - -#[test] -fn pareto() { - fn cdf(x: f64, scale: f64, alpha: f64) -> f64 { - if x <= scale { - 0.0 - } else { - 1.0 - (scale / x).powf(alpha) - } - } - - let parameters = [ - (1.0, 1.0), - (1.0, 0.1), - (1.0, 10.0), - (1.0, 100.0), - (0.1, 1.0), - (10.0, 1.0), - (100.0, 1.0), - ]; - - for (seed, (scale, alpha)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Pareto::new(scale, alpha).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, scale, alpha)); - } -} - -#[test] -fn exp() { - fn cdf(x: f64, lambda: f64) -> f64 { - 1.0 - (-lambda * x).exp() - } - - let parameters = [0.5, 1.0, 7.5, 32.0, 100.0]; - - for (seed, lambda) in parameters.into_iter().enumerate() { - let dist = rand_distr::Exp::new(lambda).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, lambda)); - } -} - -#[test] -fn weibull() { - fn cdf(x: f64, lambda: f64, k: f64) -> f64 { - if x < 0.0 { - return 0.0; - } - - 1.0 - (-(x / lambda).powf(k)).exp() - } - - let parameters = [ - (0.5, 1.0), - (1.0, 1.0), - (10.0, 0.1), - (0.1, 10.0), - (15.0, 20.0), - (1000.0, 0.01), - ]; - - for (seed, (lambda, k)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Weibull::new(lambda, k).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, lambda, k)); - } -} - -#[test] -fn gumbel() { - fn cdf(x: f64, mu: f64, beta: f64) -> f64 { - (-(-(x - mu) / beta).exp()).exp() - } - - let parameters = [ - (0.0, 1.0), - (1.0, 2.0), - (-1.0, 0.5), - (10.0, 0.1), - (100.0, 0.0001), - ]; - - for (seed, (mu, beta)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Gumbel::new(mu, beta).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, mu, beta)); - } -} - -#[test] -fn frechet() { - fn cdf(x: f64, alpha: f64, s: f64, m: f64) -> f64 { - if x < m { - return 0.0; - } - - (-((x - m) / s).powf(-alpha)).exp() - } - - let parameters = [ - (0.5, 2.0, 1.0), - (1.0, 1.0, 1.0), - (10.0, 0.1, 1.0), - (100.0, 0.0001, 1.0), - (0.9999, 2.0, 1.0), - ]; - - for (seed, (alpha, s, m)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Frechet::new(m, s, alpha).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, alpha, s, m)); - } -} - -#[test] -fn zeta() { - fn cdf(k: i64, s: f64) -> f64 { - use common::spec_func::zeta_func; - if k < 1 { - return 0.0; - } - - gen_harmonic(k as u64, s) / zeta_func(s) - } - - let parameters = [2.0, 3.7, 5.0, 100.0]; - - for (seed, s) in parameters.into_iter().enumerate() { - let dist = rand_distr::Zeta::new(s).unwrap(); - test_discrete(seed as u64, dist, |k| cdf(k, s)); - } -} - -#[test] -fn zipf() { - fn cdf(k: i64, n: u64, s: f64) -> f64 { - if k < 1 { - return 0.0; - } - if k > n as i64 { - return 1.0; - } - gen_harmonic(k as u64, s) / gen_harmonic(n, s) - } - - let parameters = [(1000, 1.0), (500, 2.0), (1000, 0.5)]; - - for (seed, (n, x)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Zipf::new(n as f64, x).unwrap(); - test_discrete(seed as u64, dist, |k| cdf(k, n, x)); - } -} - -#[test] -fn gamma() { - fn cdf(x: f64, shape: f64, scale: f64) -> f64 { - if x < 0.0 { - return 0.0; - } - - (x / scale).inc_gamma(shape) - } - - let parameters = [ - (0.5, 2.0), - (1.0, 1.0), - (10.0, 0.1), - (100.0, 0.0001), - (0.9999, 2.0), - ]; - - for (seed, (shape, scale)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Gamma::new(shape, scale).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, shape, scale)); - } -} - -#[test] -fn chi_squared() { - fn cdf(x: f64, k: f64) -> f64 { - if x < 0.0 { - return 0.0; - } - - (x / 2.0).inc_gamma(k / 2.0) - } - - let parameters = [0.1, 1.0, 2.0, 10.0, 100.0, 1000.0]; - - for (seed, k) in parameters.into_iter().enumerate() { - let dist = rand_distr::ChiSquared::new(k).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, k)); - } -} -#[test] -fn studend_t() { - fn cdf(x: f64, df: f64) -> f64 { - let h = df / (df + x.powi(2)); - let ib = 0.5 * h.inc_beta(df / 2.0, 0.5, 0.5.ln_beta(df / 2.0)); - if x < 0.0 { - ib - } else { - 1.0 - ib - } - } - - let parameters = [1.0, 10.0, 50.0]; - - for (seed, df) in parameters.into_iter().enumerate() { - let dist = rand_distr::StudentT::new(df).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, df)); - } -} - -#[test] -fn fisher_f() { - fn cdf(x: f64, m: f64, n: f64) -> f64 { - if (m == 1.0 && x <= 0.0) || x < 0.0 { - 0.0 - } else { - let k = m * x / (m * x + n); - let d1 = m / 2.0; - let d2 = n / 2.0; - k.inc_beta(d1, d2, d1.ln_beta(d2)) - } - } - - let parameters = [(1.0, 1.0), (1.0, 2.0), (2.0, 1.0), (50.0, 1.0)]; - - for (seed, (m, n)) in parameters.into_iter().enumerate() { - let dist = rand_distr::FisherF::new(m, n).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, m, n)); - } -} - -#[test] -fn beta() { - fn cdf(x: f64, alpha: f64, beta: f64) -> f64 { - if x < 0.0 { - return 0.0; - } - if x > 1.0 { - return 1.0; - } - let ln_beta_ab = alpha.ln_beta(beta); - x.inc_beta(alpha, beta, ln_beta_ab) - } - - let parameters = [(0.5, 0.5), (2.0, 3.5), (10.0, 1.0), (100.0, 50.0)]; - - for (seed, (alpha, beta)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Beta::new(alpha, beta).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, alpha, beta)); - } -} - -#[test] -fn triangular() { - fn cdf(x: f64, a: f64, b: f64, c: f64) -> f64 { - if x <= a { - 0.0 - } else if a < x && x <= c { - (x - a).powi(2) / ((b - a) * (c - a)) - } else if c < x && x < b { - 1.0 - (b - x).powi(2) / ((b - a) * (b - c)) - } else { - 1.0 - } - } - - let parameters = [ - (0.0, 1.0, 0.0001), - (0.0, 1.0, 0.9999), - (0.0, 1.0, 0.5), - (0.0, 100.0, 50.0), - (-100.0, 100.0, 0.0), - ]; - - for (seed, (a, b, c)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Triangular::new(a, b, c).unwrap(); - test_continuous(seed as u64, dist, |x| cdf(x, a, b, c)); - } -} - -fn binomial_cdf(k: i64, p: f64, n: u64) -> f64 { - if k < 0 { - return 0.0; - } - let k = k as u64; - if k >= n { - return 1.0; - } - - let a = (n - k) as f64; - let b = k as f64 + 1.0; - - let q = 1.0 - p; - - let ln_beta_ab = a.ln_beta(b); - - q.inc_beta(a, b, ln_beta_ab) -} - -#[test] -fn binomial() { - let parameters = [ - (0.5, 10), - (0.5, 100), - (0.1, 10), - (0.0000001, 1000000), - (0.0000001, 10), - (0.9999, 2), - ]; - - for (seed, (p, n)) in parameters.into_iter().enumerate() { - test_discrete(seed as u64, rand_distr::Binomial::new(n, p).unwrap(), |k| { - binomial_cdf(k, p, n) - }); - } -} - -#[test] -fn geometric() { - fn cdf(k: i64, p: f64) -> f64 { - if k < 0 { - 0.0 - } else { - 1.0 - (1.0 - p).powi(1 + k as i32) - } - } - - let parameters = [0.3, 0.5, 0.7, 0.0000001, 0.9999]; - - for (seed, p) in parameters.into_iter().enumerate() { - let dist = rand_distr::Geometric::new(p).unwrap(); - test_discrete(seed as u64, dist, |k| cdf(k, p)); - } -} - -#[test] -fn hypergeometric() { - fn cdf(x: i64, n: u64, k: u64, n_: u64) -> f64 { - let min = if n_ + k > n { n_ + k - n } else { 0 }; - let max = k.min(n_); - if x < min as i64 { - return 0.0; - } else if x >= max as i64 { - return 1.0; - } - - (min..x as u64 + 1).fold(0.0, |acc, k_| { - acc + (ln_binomial(k, k_) + ln_binomial(n - k, n_ - k_) - ln_binomial(n, n_)).exp() - }) - } - - let parameters = [ - (15, 13, 10), - (25, 15, 5), - (60, 10, 7), - (70, 20, 50), - (100, 50, 10), - (100, 50, 49), - ]; - - for (seed, (n, k, n_)) in parameters.into_iter().enumerate() { - let dist = rand_distr::Hypergeometric::new(n, k, n_).unwrap(); - test_discrete(seed as u64, dist, |x| cdf(x, n, k, n_)); - } -} - -#[test] -fn poisson() { - use rand_distr::Poisson; - fn cdf(k: i64, lambda: f64) -> f64 { - use common::spec_func::gamma_lr; - - if k < 0 || lambda <= 0.0 { - return 0.0; - } - - 1.0 - gamma_lr(k as f64 + 1.0, lambda) - } - let parameters = [ - 0.1, 1.0, 7.5, - // 1e9, passed case but too slow - // 1.844E+19, // fail case - ]; - - for (seed, lambda) in parameters.into_iter().enumerate() { - let dist = Poisson::new(lambda).unwrap(); - test_discrete::, _>(seed as u64, dist, |k| cdf(k, lambda)); - } -} - -fn ln_factorial(n: u64) -> f64 { - (n as f64 + 1.0).lgamma().0 -} - -fn ln_binomial(n: u64, k: u64) -> f64 { - ln_factorial(n) - ln_factorial(k) - ln_factorial(n - k) -} - -fn gen_harmonic(n: u64, m: f64) -> f64 { - match n { - 0 => 1.0, - _ => (0..n).fold(0.0, |acc, x| acc + (x as f64 + 1.0).powf(-m)), - } -} - -/// [1] Patefield, M. (2000). Fast and Accurate Calculation of Owen’s T Function. -/// Journal of Statistical Software, 5(5), 1–25. -/// https://doi.org/10.18637/jss.v005.i05 -/// -/// This function is ported to Rust from the Fortran code provided in the paper -fn owen_t(h: f64, a: f64) -> f64 { - let absh = h.abs(); - let absa = a.abs(); - let ah = absa * absh; - - let mut t; - if absa <= 1.0 { - t = tf(absh, absa, ah); - } else if absh <= 0.67 { - t = 0.25 - znorm1(absh) * znorm1(ah) - tf(ah, 1.0 / absa, absh); - } else { - let normh = znorm2(absh); - let normah = znorm2(ah); - t = 0.5 * (normh + normah) - normh * normah - tf(ah, 1.0 / absa, absh); - } - - if a < 0.0 { - t = -t; - } - - fn tf(h: f64, a: f64, ah: f64) -> f64 { - let rtwopi = 0.159_154_943_091_895_35; - let rrtpi = 0.398_942_280_401_432_7; - - let c2 = [ - 0.999_999_999_999_999_9, - -0.999_999_999_999_888, - 0.999_999_999_982_907_5, - -0.999_999_998_962_825, - 0.999_999_966_604_593_7, - -0.999_999_339_862_724_7, - 0.999_991_256_111_369_6, - -0.999_917_776_244_633_8, - 0.999_428_355_558_701_4, - -0.996_973_117_207_23, - 0.987_514_480_372_753, - -0.959_158_579_805_728_8, - 0.892_463_055_110_067_1, - -0.768_934_259_904_64, - 0.588_935_284_684_846_9, - -0.383_803_451_604_402_55, - 0.203_176_017_010_453, - -8.281_363_160_700_499e-2, - 2.416_798_473_575_957_8e-2, - -4.467_656_666_397_183e-3, - 3.914_116_940_237_383_6e-4, - ]; - - let pts = [ - 3.508_203_967_645_171_6e-3, - 3.127_904_233_803_075_6e-2, - 8.526_682_628_321_945e-2, - 0.162_450_717_308_122_77, - 0.258_511_960_491_254_36, - 0.368_075_538_406_975_3, - 0.485_010_929_056_047, - 0.602_775_141_526_185_7, - 0.714_778_842_177_532_3, - 0.814_755_109_887_601, - 0.897_110_297_559_489_7, - 0.957_238_080_859_442_6, - 0.991_788_329_746_297, - ]; - - let wts = [ - 1.883_143_811_532_350_3e-2, - 1.856_708_624_397_765e-2, - 1.804_209_346_122_338_5e-2, - 1.726_382_960_639_875_2e-2, - 1.624_321_997_598_985_8e-2, - 1.499_459_203_411_670_5e-2, - 1.353_547_446_966_209e-2, - 1.188_635_160_582_016_5e-2, - 1.007_037_724_277_743_2e-2, - 8.113_054_574_229_958e-3, - 6.041_900_952_847_024e-3, - 3.886_221_701_074_205_7e-3, - 1.679_303_108_454_609e-3, - ]; - - let hrange = [ - 0.02, 0.06, 0.09, 0.125, 0.26, 0.4, 0.6, 1.6, 1.7, 2.33, 2.4, 3.36, 3.4, 4.8, - ]; - let arange = [0.025, 0.09, 0.15, 0.36, 0.5, 0.9, 0.99999]; - - let select = [ - [1, 1, 2, 13, 13, 13, 13, 13, 13, 13, 13, 16, 16, 16, 9], - [1, 2, 2, 3, 3, 5, 5, 14, 14, 15, 15, 16, 16, 16, 9], - [2, 2, 3, 3, 3, 5, 5, 15, 15, 15, 15, 16, 16, 16, 10], - [2, 2, 3, 5, 5, 5, 5, 7, 7, 16, 16, 16, 16, 16, 10], - [2, 3, 3, 5, 5, 6, 6, 8, 8, 17, 17, 17, 12, 12, 11], - [2, 3, 5, 5, 5, 6, 6, 8, 8, 17, 17, 17, 12, 12, 12], - [2, 3, 4, 4, 6, 6, 8, 8, 17, 17, 17, 17, 17, 12, 12], - [2, 3, 4, 4, 6, 6, 18, 18, 18, 18, 17, 17, 17, 12, 12], - ]; - - let ihint = hrange.iter().position(|&r| h < r).unwrap_or(14); - - let iaint = arange.iter().position(|&r| a < r).unwrap_or(7); - - let icode = select[iaint][ihint]; - let m = [ - 2, 3, 4, 5, 7, 10, 12, 18, 10, 20, 30, 20, 4, 7, 8, 20, 13, 0, - ][icode - 1]; - let method = [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5, 6][icode - 1]; - - match method { - 1 => { - let hs = -0.5 * h * h; - let dhs = hs.exp(); - let as_ = a * a; - let mut j = 1; - let mut jj = 1; - let mut aj = rtwopi * a; - let mut tf = rtwopi * a.atan(); - let mut dj = dhs - 1.0; - let mut gj = hs * dhs; - loop { - tf += dj * aj / (jj as f64); - if j >= m { - return tf; - } - j += 1; - jj += 2; - aj *= as_; - dj = gj - dj; - gj *= hs / (j as f64); - } - } - 2 => { - let maxii = m + m + 1; - let mut ii = 1; - let mut tf = 0.0; - let hs = h * h; - let as_ = -a * a; - let mut vi = rrtpi * a * (-0.5 * ah * ah).exp(); - let mut z = znorm1(ah) / h; - let y = 1.0 / hs; - loop { - tf += z; - if ii >= maxii { - tf *= rrtpi * (-0.5 * hs).exp(); - return tf; - } - z = y * (vi - (ii as f64) * z); - vi *= as_; - ii += 2; - } - } - 3 => { - let mut i = 1; - let mut ii = 1; - let mut tf = 0.0; - let hs = h * h; - let as_ = a * a; - let mut vi = rrtpi * a * (-0.5 * ah * ah).exp(); - let mut zi = znorm1(ah) / h; - let y = 1.0 / hs; - loop { - tf += zi * c2[i - 1]; - if i > m { - tf *= rrtpi * (-0.5 * hs).exp(); - return tf; - } - zi = y * ((ii as f64) * zi - vi); - vi *= as_; - i += 1; - ii += 2; - } - } - 4 => { - let maxii = m + m + 1; - let mut ii = 1; - let mut tf = 0.0; - let hs = h * h; - let as_ = -a * a; - let mut ai = rtwopi * a * (-0.5 * hs * (1.0 - as_)).exp(); - let mut yi = 1.0; - loop { - tf += ai * yi; - if ii >= maxii { - return tf; - } - ii += 2; - yi = (1.0 - hs * yi) / (ii as f64); - ai *= as_; - } - } - 5 => { - let mut tf = 0.0; - let as_ = a * a; - let hs = -0.5 * h * h; - for i in 0..m { - let r = 1.0 + as_ * pts[i]; - tf += wts[i] * (hs * r).exp() / r; - } - tf *= a; - tf - } - 6 => { - let normh = znorm2(h); - let mut tf = 0.5 * normh * (1.0 - normh); - let y = 1.0 - a; - let r = (y / (1.0 + a)).atan(); - if r != 0.0 { - tf -= rtwopi * r * (-0.5 * y * h * h / r).exp(); - } - tf - } - _ => 0.0, - } - } - - // P(0 ≤ Z ≤ x) - fn znorm1(x: f64) -> f64 { - phi(x) - 0.5 - } - - // P(x ≤ Z < ∞) - fn znorm2(x: f64) -> f64 { - 1.0 - phi(x) - } - - t -} - -/// standard normal cdf -fn phi(x: f64) -> f64 { - normal_cdf(x, 0.0, 1.0) -} diff --git a/rand_distr/tests/common/mod.rs b/rand_distr/tests/common/mod.rs deleted file mode 100644 index 4fa8db7de6a..00000000000 --- a/rand_distr/tests/common/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod spec_func; diff --git a/rand_distr/tests/common/spec_func.rs b/rand_distr/tests/common/spec_func.rs deleted file mode 100644 index bd39a79fb47..00000000000 --- a/rand_distr/tests/common/spec_func.rs +++ /dev/null @@ -1,155 +0,0 @@ -// MIT License - -// Copyright (c) 2016 Michael Ma - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -/// https://docs.rs/statrs/latest/src/statrs/function/gamma.rs.html#260-347 -use special::Primitive; - -pub fn gamma_lr(a: f64, x: f64) -> f64 { - if a.is_nan() || x.is_nan() { - return f64::NAN; - } - - if a <= 0.0 || a == f64::INFINITY { - panic!("a must be positive and finite"); - } - if x <= 0.0 || x == f64::INFINITY { - panic!("x must be positive and finite"); - } - - const ACC: f64 = 0.0000000000000011102230246251565; - - if a.abs() < ACC { - return 1.0; - } - - if x.abs() < ACC { - return 0.0; - } - - let eps = 0.000000000000001; - let big = 4503599627370496.0; - let big_inv = 2.220_446_049_250_313e-16; - - let ax = a * x.ln() - x - a.lgamma().0; - if ax < -709.782_712_893_384 { - if a < x { - return 1.0; - } - return 0.0; - } - if x <= 1.0 || x <= a { - let mut r2 = a; - let mut c2 = 1.0; - let mut ans2 = 1.0; - loop { - r2 += 1.0; - c2 *= x / r2; - ans2 += c2; - - if c2 / ans2 <= eps { - break; - } - } - return ax.exp() * ans2 / a; - } - - let mut y = 1.0 - a; - let mut z = x + y + 1.0; - let mut c = 0; - - let mut p3 = 1.0; - let mut q3 = x; - let mut p2 = x + 1.0; - let mut q2 = z * x; - let mut ans = p2 / q2; - - loop { - y += 1.0; - z += 2.0; - c += 1; - let yc = y * f64::from(c); - - let p = p2 * z - p3 * yc; - let q = q2 * z - q3 * yc; - - p3 = p2; - p2 = p; - q3 = q2; - q2 = q; - - if p.abs() > big { - p3 *= big_inv; - p2 *= big_inv; - q3 *= big_inv; - q2 *= big_inv; - } - - if q != 0.0 { - let nextans = p / q; - let error = ((ans - nextans) / nextans).abs(); - ans = nextans; - - if error <= eps { - break; - } - } - } - 1.0 - ax.exp() * ans -} - -/// https://docs.rs/spfunc/latest/src/spfunc/zeta.rs.html#20-43 -pub fn zeta_func(s: f64) -> f64 { - fn update_akn(akn: &mut Vec, s: f64) { - let n = akn.len() - 1; - - let n1 = n as f64 + 1.0; - - akn.iter_mut().enumerate().for_each(|(k, a)| { - let num = n1; - let den = n + 1 - k; - *a *= num / den as f64; - }); - let p1 = -1.0 / n1; - let p2 = (n1 / (n1 + 1.0)).powf(s); - - akn.push(p1 * p2 * akn[n]); - } - - if s == 1.0 { - return f64::INFINITY; - } - - let mut akn = vec![1.0]; - let mut two_pow = 0.5; - let head = 1.0 / (1.0 - 2.0.powf(1.0 - s)); - let mut tail_prev = 0.0; - let mut tail = two_pow * akn[0]; - - while (tail - tail_prev).abs() >= f64::EPSILON { - update_akn(&mut akn, s); - two_pow /= 2.0; - tail_prev = tail; - tail += two_pow * akn.iter().sum::(); - } - - head * tail -} diff --git a/rand_distr/tests/pdf.rs b/rand_distr/tests/pdf.rs deleted file mode 100644 index 1bbbd32d55b..00000000000 --- a/rand_distr/tests/pdf.rs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2021 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![allow(clippy::float_cmp)] - -use average::Histogram; -use rand::{Rng, SeedableRng}; -use rand_distr::{Normal, SkewNormal}; - -const HIST_LEN: usize = 100; -average::define_histogram!(hist, crate::HIST_LEN); -use hist::Histogram as Histogram100; - -mod sparkline; - -#[test] -fn normal() { - const N_SAMPLES: u64 = 1_000_000; - const MEAN: f64 = 2.; - const STD_DEV: f64 = 0.5; - const MIN_X: f64 = -1.; - const MAX_X: f64 = 5.; - - let dist = Normal::new(MEAN, STD_DEV).unwrap(); - let mut hist = Histogram100::with_const_width(MIN_X, MAX_X); - let mut rng = rand::rngs::SmallRng::seed_from_u64(1); - - for _ in 0..N_SAMPLES { - let _ = hist.add(rng.sample(dist)); // Ignore out-of-range values - } - - println!( - "Sampled normal distribution:\n{}", - sparkline::render_u64_as_string(hist.bins()) - ); - - fn pdf(x: f64) -> f64 { - (-0.5 * ((x - MEAN) / STD_DEV).powi(2)).exp() - / (STD_DEV * (2. * core::f64::consts::PI).sqrt()) - } - - let mut bin_centers = hist.centers(); - let mut expected = [0.; HIST_LEN]; - for e in &mut expected[..] { - *e = pdf(bin_centers.next().unwrap()); - } - - println!( - "Expected normal distribution:\n{}", - sparkline::render_u64_as_string(hist.bins()) - ); - - let mut diff = [0.; HIST_LEN]; - for (i, n) in hist.normalized_bins().enumerate() { - let bin = n / (N_SAMPLES as f64); - diff[i] = (bin - expected[i]).abs(); - } - - println!( - "Difference:\n{}", - sparkline::render_f64_as_string(&diff[..]) - ); - println!( - "max diff: {:?}", - diff.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)) - ); - - // Check that the differences are significantly smaller than the expected error. - let mut expected_error = [0.; HIST_LEN]; - // Calculate error from histogram - for (err, var) in expected_error.iter_mut().zip(hist.variances()) { - *err = var.sqrt() / (N_SAMPLES as f64); - } - // Normalize error by bin width - for (err, width) in expected_error.iter_mut().zip(hist.widths()) { - *err /= width; - } - // TODO: Calculate error from distribution cutoff / normalization - - println!( - "max expected_error: {:?}", - expected_error - .iter() - .fold(f64::NEG_INFINITY, |a, &b| a.max(b)) - ); - for (&d, &e) in diff.iter().zip(expected_error.iter()) { - // Difference larger than 4 standard deviations or cutoff - let tol = (4. * e).max(1e-4); - assert!(d <= tol, "Difference = {} * tol", d / tol); - } -} - -#[test] -fn skew_normal() { - const N_SAMPLES: u64 = 1_000_000; - const LOCATION: f64 = 2.; - const SCALE: f64 = 0.5; - const SHAPE: f64 = -3.0; - const MIN_X: f64 = -1.; - const MAX_X: f64 = 4.; - - let dist = SkewNormal::new(LOCATION, SCALE, SHAPE).unwrap(); - let mut hist = Histogram100::with_const_width(MIN_X, MAX_X); - let mut rng = rand::rngs::SmallRng::seed_from_u64(1); - - for _ in 0..N_SAMPLES { - let _ = hist.add(rng.sample(dist)); // Ignore out-of-range values - } - - println!( - "Sampled skew normal distribution:\n{}", - sparkline::render_u64_as_string(hist.bins()) - ); - - use special::Error; - fn pdf(x: f64) -> f64 { - let x_normalized = (x - LOCATION) / SCALE; - let normal_density_x = - (-0.5 * (x_normalized).powi(2)).exp() / (2. * core::f64::consts::PI).sqrt(); - let normal_distribution_x = - 0.5 * (1.0 + (SHAPE * x_normalized / core::f64::consts::SQRT_2).error()); - 2.0 / SCALE * normal_density_x * normal_distribution_x - } - - let mut bin_centers = hist.centers(); - let mut expected = [0.; HIST_LEN]; - for e in &mut expected[..] { - *e = pdf(bin_centers.next().unwrap()); - } - - println!( - "Expected skew normal distribution:\n{}", - sparkline::render_u64_as_string(hist.bins()) - ); - - let mut diff = [0.; HIST_LEN]; - for (i, n) in hist.normalized_bins().enumerate() { - let bin = n / (N_SAMPLES as f64); - diff[i] = (bin - expected[i]).abs(); - } - - println!( - "Difference:\n{}", - sparkline::render_f64_as_string(&diff[..]) - ); - println!( - "max diff: {:?}", - diff.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)) - ); - - // Check that the differences are significantly smaller than the expected error. - let mut expected_error = [0.; HIST_LEN]; - // Calculate error from histogram - for (err, var) in expected_error.iter_mut().zip(hist.variances()) { - *err = var.sqrt() / (N_SAMPLES as f64); - } - // Normalize error by bin width - for (err, width) in expected_error.iter_mut().zip(hist.widths()) { - *err /= width; - } - // TODO: Calculate error from distribution cutoff / normalization - - println!( - "max expected_error: {:?}", - expected_error - .iter() - .fold(f64::NEG_INFINITY, |a, &b| a.max(b)) - ); - for (&d, &e) in diff.iter().zip(expected_error.iter()) { - // Difference larger than 4 standard deviations or cutoff - let tol = (4. * e).max(1e-4); - assert!(d <= tol, "Difference = {} * tol", d / tol); - } -} diff --git a/rand_distr/tests/sparkline.rs b/rand_distr/tests/sparkline.rs deleted file mode 100644 index ec0ee98de98..00000000000 --- a/rand_distr/tests/sparkline.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2021 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -/// Number of ticks. -const N: usize = 8; -/// Ticks used for the sparkline. -static TICKS: [char; N] = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']; - -/// Render a sparkline of `data` into `buffer`. -pub fn render_u64(data: &[u64], buffer: &mut String) { - match data.len() { - 0 => { - return; - } - 1 => { - if data[0] == 0 { - buffer.push(TICKS[0]); - } else { - buffer.push(TICKS[N - 1]); - } - return; - } - _ => {} - } - let max = data.iter().max().unwrap(); - let min = data.iter().min().unwrap(); - let scale = ((N - 1) as f64) / ((max - min) as f64); - for i in data { - let tick = (((i - min) as f64) * scale) as usize; - buffer.push(TICKS[tick]); - } -} - -/// Calculate the required capacity for the sparkline, given the length of the -/// input data. -pub fn required_capacity(len: usize) -> usize { - len * TICKS[0].len_utf8() -} - -/// Render a sparkline of `data` into a newly allocated string. -pub fn render_u64_as_string(data: &[u64]) -> String { - let cap = required_capacity(data.len()); - let mut s = String::with_capacity(cap); - render_u64(data, &mut s); - debug_assert_eq!(s.capacity(), cap); - s -} - -/// Render a sparkline of `data` into `buffer`. -pub fn render_f64(data: &[f64], buffer: &mut String) { - match data.len() { - 0 => { - return; - } - 1 => { - if data[0] == 0. { - buffer.push(TICKS[0]); - } else { - buffer.push(TICKS[N - 1]); - } - return; - } - _ => {} - } - for x in data { - assert!(x.is_finite(), "can only render finite values"); - } - let max = data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)); - let min = data.iter().fold(f64::INFINITY, |a, &b| a.min(b)); - let scale = ((N - 1) as f64) / (max - min); - for x in data { - let tick = ((x - min) * scale) as usize; - buffer.push(TICKS[tick]); - } -} - -/// Render a sparkline of `data` into a newly allocated string. -pub fn render_f64_as_string(data: &[f64]) -> String { - let cap = required_capacity(data.len()); - let mut s = String::with_capacity(cap); - render_f64(data, &mut s); - debug_assert_eq!(s.capacity(), cap); - s -} - -#[cfg(test)] -mod tests { - #[test] - fn render_u64() { - let data = [2, 250, 670, 890, 2, 430, 11, 908, 123, 57]; - let mut s = String::with_capacity(super::required_capacity(data.len())); - super::render_u64(&data, &mut s); - println!("{}", s); - assert_eq!("▁▂▆▇▁▄▁█▁▁", &s); - } - - #[test] - fn render_u64_as_string() { - let data = [2, 250, 670, 890, 2, 430, 11, 908, 123, 57]; - let s = super::render_u64_as_string(&data); - println!("{}", s); - assert_eq!("▁▂▆▇▁▄▁█▁▁", &s); - } - - #[test] - fn render_f64() { - let data = [2., 250., 670., 890., 2., 430., 11., 908., 123., 57.]; - let mut s = String::with_capacity(super::required_capacity(data.len())); - super::render_f64(&data, &mut s); - println!("{}", s); - assert_eq!("▁▂▆▇▁▄▁█▁▁", &s); - } - - #[test] - fn render_f64_as_string() { - let data = [2., 250., 670., 890., 2., 430., 11., 908., 123., 57.]; - let s = super::render_f64_as_string(&data); - println!("{}", s); - assert_eq!("▁▂▆▇▁▄▁█▁▁", &s); - } -} diff --git a/rand_distr/tests/uniformity.rs b/rand_distr/tests/uniformity.rs deleted file mode 100644 index 4818eac9907..00000000000 --- a/rand_distr/tests/uniformity.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![allow(clippy::float_cmp)] - -use average::Histogram; -use rand::prelude::*; - -const N_BINS: usize = 100; -const N_SAMPLES: u32 = 1_000_000; -const TOL: f64 = 1e-3; -average::define_histogram!(hist, 100); -use hist::Histogram as Histogram100; - -#[test] -fn unit_sphere() { - const N_DIM: usize = 3; - let h = Histogram100::with_const_width(-1., 1.); - let mut histograms = [h.clone(), h.clone(), h]; - let dist = rand_distr::UnitSphere; - let mut rng = rand_pcg::Pcg32::from_os_rng(); - for _ in 0..N_SAMPLES { - let v: [f64; 3] = dist.sample(&mut rng); - for i in 0..N_DIM { - histograms[i] - .add(v[i]) - .map_err(|e| { - println!("v: {}", v[i]); - e - }) - .unwrap(); - } - } - for h in &histograms { - let sum: u64 = h.bins().iter().sum(); - println!("{:?}", h); - for &b in h.bins() { - let p = (b as f64) / (sum as f64); - assert!((p - 1.0 / (N_BINS as f64)).abs() < TOL, "{}", p); - } - } -} - -#[test] -fn unit_circle() { - use core::f64::consts::PI; - let mut h = Histogram100::with_const_width(-PI, PI); - let dist = rand_distr::UnitCircle; - let mut rng = rand_pcg::Pcg32::from_os_rng(); - for _ in 0..N_SAMPLES { - let v: [f64; 2] = dist.sample(&mut rng); - h.add(v[0].atan2(v[1])).unwrap(); - } - let sum: u64 = h.bins().iter().sum(); - println!("{:?}", h); - for &b in h.bins() { - let p = (b as f64) / (sum as f64); - assert!((p - 1.0 / (N_BINS as f64)).abs() < TOL, "{}", p); - } -} From 3f984dd9cba225d688c1779a60d3109e97e0510b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 18 Nov 2024 16:55:03 +0000 Subject: [PATCH 427/443] =?UTF-8?q?Rename=20`Standard`=20=E2=86=92=20`Stan?= =?UTF-8?q?dardUniform`=20(#1526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also remove impl for Option --- CHANGELOG.md | 2 + README.md | 2 +- benches/benches/array.rs | 6 +- benches/benches/bool.rs | 2 +- benches/benches/generators.rs | 2 +- benches/benches/standard.rs | 6 +- rand_distr/src/cauchy.rs | 12 +-- rand_distr/src/inverse_gaussian.rs | 8 +- rand_distr/src/lib.rs | 4 +- rand_distr/src/normal_inverse_gaussian.rs | 8 +- rand_distr/src/poisson.rs | 14 +-- rand_distr/src/triangular.rs | 10 +- rand_distr/src/zeta.rs | 10 +- rand_distr/src/zipf.rs | 12 +-- src/distr/distribution.rs | 8 +- src/distr/float.rs | 28 +++--- src/distr/integer.rs | 46 ++++----- src/distr/mod.rs | 116 ++++++++++------------ src/distr/other.rs | 68 +++++-------- src/distr/uniform.rs | 12 ++- src/distr/uniform_float.rs | 2 +- src/distr/uniform_int.rs | 6 +- src/lib.rs | 15 ++- src/rng.rs | 34 +++---- src/rngs/mock.rs | 4 +- 25 files changed, 212 insertions(+), 225 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c7353174c..44603eeb537 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `rand::thread_rng()` to `rand::rng()`, and remove from the prelude (#1506) - Remove `rand::random()` from the prelude (#1506) - Rename `Rng::gen_range` to `random_range`, `gen_bool` to `random_bool`, `gen_ratio` to `random_ratio` (#1505) +- Rename `Standard` to `StandardUniform` (#1526) +- Remove impl of `Distribution>` for `Standard` (#1526) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/README.md b/README.md index 58f363896b3..b8e089099f1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Rand is a set of crates supporting (pseudo-)random generators: With broad support for random value generation and random processes: -- [`Standard`](https://docs.rs/rand/latest/rand/distributions/struct.Standard.html) random value sampling, +- [`StandardUniform`](https://docs.rs/rand/latest/rand/distributions/struct.StandardUniform.html) random value sampling, [`Uniform`](https://docs.rs/rand/latest/rand/distributions/struct.Uniform.html)-ranged value sampling and [more](https://docs.rs/rand/latest/rand/distr/index.html) - Samplers for a large number of non-uniform random number distributions via our own diff --git a/benches/benches/array.rs b/benches/benches/array.rs index a21e902cb95..063516337bf 100644 --- a/benches/benches/array.rs +++ b/benches/benches/array.rs @@ -9,7 +9,7 @@ //! Generating/filling arrays and iterators of output use criterion::{criterion_group, criterion_main, Criterion}; -use rand::distr::Standard; +use rand::distr::StandardUniform; use rand::prelude::*; use rand_pcg::Pcg64Mcg; @@ -36,7 +36,7 @@ pub fn bench(c: &mut Criterion) { g.bench_function("u16_sample_iter", |b| { let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| { - let v: Vec = Standard.sample_iter(&mut rng).take(512).collect(); + let v: Vec = StandardUniform.sample_iter(&mut rng).take(512).collect(); v }); }); @@ -70,7 +70,7 @@ pub fn bench(c: &mut Criterion) { g.bench_function("u64_sample_iter", |b| { let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| { - let v: Vec = Standard.sample_iter(&mut rng).take(128).collect(); + let v: Vec = StandardUniform.sample_iter(&mut rng).take(128).collect(); v }); }); diff --git a/benches/benches/bool.rs b/benches/benches/bool.rs index 989a938d9f8..8ff8c676024 100644 --- a/benches/benches/bool.rs +++ b/benches/benches/bool.rs @@ -28,7 +28,7 @@ pub fn bench(c: &mut Criterion) { g.bench_function("standard", |b| { let mut rng = Pcg32::from_rng(&mut rand::rng()); - b.iter(|| rng.sample::(rand::distr::Standard)) + b.iter(|| rng.sample::(rand::distr::StandardUniform)) }); g.bench_function("const", |b| { diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index cdfab9b2f7e..95361cd203f 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -171,7 +171,7 @@ pub fn init_from_seed(c: &mut Criterion) { fn bench(g: &mut BenchmarkGroup, name: &str) where - rand::distr::Standard: Distribution<::Seed>, + rand::distr::StandardUniform: Distribution<::Seed>, { g.bench_function(name, |b| { let mut rng = Pcg32::from_os_rng(); diff --git a/benches/benches/standard.rs b/benches/benches/standard.rs index cd88ea66a28..ffe7ea5de1f 100644 --- a/benches/benches/standard.rs +++ b/benches/benches/standard.rs @@ -9,7 +9,7 @@ use core::time::Duration; use criterion::measurement::WallTime; use criterion::{criterion_group, criterion_main, BenchmarkGroup, Criterion}; -use rand::distr::{Alphanumeric, Standard}; +use rand::distr::{Alphanumeric, StandardUniform}; use rand::prelude::*; use rand_distr::{Open01, OpenClosed01}; use rand_pcg::Pcg64Mcg; @@ -34,14 +34,14 @@ where } pub fn bench(c: &mut Criterion) { - let mut g = c.benchmark_group("Standard"); + let mut g = c.benchmark_group("StandardUniform"); g.sample_size(1000); g.warm_up_time(Duration::from_millis(500)); g.measurement_time(Duration::from_millis(1000)); macro_rules! do_ty { ($t:ty) => { - bench_ty::<$t, Standard>(&mut g, stringify!($t)); + bench_ty::<$t, StandardUniform>(&mut g, stringify!($t)); }; ($t:ty, $($tt:ty),*) => { do_ty!($t); diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 4042b4fbf70..8f0faad3863 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -9,7 +9,7 @@ //! The Cauchy distribution `Cauchy(x₀, γ)`. -use crate::{Distribution, Standard}; +use crate::{Distribution, StandardUniform}; use core::fmt; use num_traits::{Float, FloatConst}; use rand::Rng; @@ -58,7 +58,7 @@ use rand::Rng; pub struct Cauchy where F: Float + FloatConst, - Standard: Distribution, + StandardUniform: Distribution, { median: F, scale: F, @@ -85,7 +85,7 @@ impl std::error::Error for Error {} impl Cauchy where F: Float + FloatConst, - Standard: Distribution, + StandardUniform: Distribution, { /// Construct a new `Cauchy` with the given shape parameters /// `median` the peak location and `scale` the scale factor. @@ -100,11 +100,11 @@ where impl Distribution for Cauchy where F: Float + FloatConst, - Standard: Distribution, + StandardUniform: Distribution, { fn sample(&self, rng: &mut R) -> F { // sample from [0, 1) - let x = Standard.sample(rng); + let x = StandardUniform.sample(rng); // get standard cauchy random number // note that π/2 is not exactly representable, even if x=0.5 the result is finite let comp_dev = (F::PI() * x).tan(); @@ -166,7 +166,7 @@ mod test { fn value_stability() { fn gen_samples(m: F, s: F, buf: &mut [F]) where - Standard: Distribution, + StandardUniform: Distribution, { let distr = Cauchy::new(m, s).unwrap(); let mut rng = crate::test::rng(353); diff --git a/rand_distr/src/inverse_gaussian.rs b/rand_distr/src/inverse_gaussian.rs index 8614a15e668..354c2e05986 100644 --- a/rand_distr/src/inverse_gaussian.rs +++ b/rand_distr/src/inverse_gaussian.rs @@ -1,6 +1,6 @@ //! The inverse Gaussian distribution `IG(μ, λ)`. -use crate::{Distribution, Standard, StandardNormal}; +use crate::{Distribution, StandardNormal, StandardUniform}; use core::fmt; use num_traits::Float; use rand::Rng; @@ -53,7 +53,7 @@ pub struct InverseGaussian where F: Float, StandardNormal: Distribution, - Standard: Distribution, + StandardUniform: Distribution, { mean: F, shape: F, @@ -63,7 +63,7 @@ impl InverseGaussian where F: Float, StandardNormal: Distribution, - Standard: Distribution, + StandardUniform: Distribution, { /// Construct a new `InverseGaussian` distribution with the given mean and /// shape. @@ -85,7 +85,7 @@ impl Distribution for InverseGaussian where F: Float, StandardNormal: Distribution, - Standard: Distribution, + StandardUniform: Distribution, { #[allow(clippy::many_single_char_names)] fn sample(&self, rng: &mut R) -> F diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index 03fad85c919..efd316b09c0 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -34,7 +34,7 @@ //! The following are re-exported: //! //! - The [`Distribution`] trait and [`DistIter`] helper type -//! - The [`Standard`], [`Alphanumeric`], [`Uniform`], [`OpenClosed01`], +//! - The [`StandardUniform`], [`Alphanumeric`], [`Uniform`], [`OpenClosed01`], //! [`Open01`], [`Bernoulli`], and [`WeightedIndex`] distributions //! //! ## Distributions @@ -95,7 +95,7 @@ use rand::Rng; pub use rand::distr::{ uniform, Alphanumeric, Bernoulli, BernoulliError, DistIter, Distribution, Open01, OpenClosed01, - Standard, Uniform, + StandardUniform, Uniform, }; pub use self::beta::{Beta, Error as BetaError}; diff --git a/rand_distr/src/normal_inverse_gaussian.rs b/rand_distr/src/normal_inverse_gaussian.rs index cd9c1781ce4..6ad2e58fe65 100644 --- a/rand_distr/src/normal_inverse_gaussian.rs +++ b/rand_distr/src/normal_inverse_gaussian.rs @@ -1,4 +1,4 @@ -use crate::{Distribution, InverseGaussian, Standard, StandardNormal}; +use crate::{Distribution, InverseGaussian, StandardNormal, StandardUniform}; use core::fmt; use num_traits::Float; use rand::Rng; @@ -54,7 +54,7 @@ pub struct NormalInverseGaussian where F: Float, StandardNormal: Distribution, - Standard: Distribution, + StandardUniform: Distribution, { beta: F, inverse_gaussian: InverseGaussian, @@ -64,7 +64,7 @@ impl NormalInverseGaussian where F: Float, StandardNormal: Distribution, - Standard: Distribution, + StandardUniform: Distribution, { /// Construct a new `NormalInverseGaussian` distribution with the given alpha (tail heaviness) and /// beta (asymmetry) parameters. @@ -94,7 +94,7 @@ impl Distribution for NormalInverseGaussian where F: Float, StandardNormal: Distribution, - Standard: Distribution, + StandardUniform: Distribution, { fn sample(&self, rng: &mut R) -> F where diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 20b41ace64a..3e4421259bd 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -9,7 +9,7 @@ //! The Poisson distribution `Poisson(λ)`. -use crate::{Cauchy, Distribution, Standard}; +use crate::{Cauchy, Distribution, StandardUniform}; use core::fmt; use num_traits::{Float, FloatConst}; use rand::Rng; @@ -55,7 +55,7 @@ use rand::Rng; pub struct Poisson(Method) where F: Float + FloatConst, - Standard: Distribution; + StandardUniform: Distribution; /// Error type returned from [`Poisson::new`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -130,7 +130,7 @@ enum Method { impl Poisson where F: Float + FloatConst, - Standard: Distribution, + StandardUniform: Distribution, { /// Construct a new `Poisson` with the given shape parameter /// `lambda`. @@ -172,7 +172,7 @@ where impl Distribution for KnuthMethod where F: Float + FloatConst, - Standard: Distribution, + StandardUniform: Distribution, { fn sample(&self, rng: &mut R) -> F { let mut result = F::one(); @@ -188,7 +188,7 @@ where impl Distribution for RejectionMethod where F: Float + FloatConst, - Standard: Distribution, + StandardUniform: Distribution, { fn sample(&self, rng: &mut R) -> F { // The algorithm from Numerical Recipes in C @@ -238,7 +238,7 @@ where impl Distribution for Poisson where F: Float + FloatConst, - Standard: Distribution, + StandardUniform: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> F { @@ -255,7 +255,7 @@ mod test { fn test_poisson_avg_gen(lambda: F, tol: F) where - Standard: Distribution, + StandardUniform: Distribution, { let poisson = Poisson::new(lambda).unwrap(); let mut rng = crate::test::rng(123); diff --git a/rand_distr/src/triangular.rs b/rand_distr/src/triangular.rs index 9342ba024a1..05a46e57ecf 100644 --- a/rand_distr/src/triangular.rs +++ b/rand_distr/src/triangular.rs @@ -7,7 +7,7 @@ // except according to those terms. //! The triangular distribution. -use crate::{Distribution, Standard}; +use crate::{Distribution, StandardUniform}; use core::fmt; use num_traits::Float; use rand::Rng; @@ -43,7 +43,7 @@ use rand::Rng; pub struct Triangular where F: Float, - Standard: Distribution, + StandardUniform: Distribution, { min: F, max: F, @@ -76,7 +76,7 @@ impl std::error::Error for TriangularError {} impl Triangular where F: Float, - Standard: Distribution, + StandardUniform: Distribution, { /// Set up the Triangular distribution with defined `min`, `max` and `mode`. #[inline] @@ -94,11 +94,11 @@ where impl Distribution for Triangular where F: Float, - Standard: Distribution, + StandardUniform: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> F { - let f: F = rng.sample(Standard); + let f: F = rng.sample(StandardUniform); let diff_mode_min = self.mode - self.min; let range = self.max - self.min; let f_range = f * range; diff --git a/rand_distr/src/zeta.rs b/rand_distr/src/zeta.rs index e06208c94ea..f93f167d7c3 100644 --- a/rand_distr/src/zeta.rs +++ b/rand_distr/src/zeta.rs @@ -8,7 +8,7 @@ //! The Zeta distribution. -use crate::{Distribution, Standard}; +use crate::{Distribution, StandardUniform}; use core::fmt; use num_traits::Float; use rand::{distr::OpenClosed01, Rng}; @@ -68,7 +68,7 @@ use rand::{distr::OpenClosed01, Rng}; pub struct Zeta where F: Float, - Standard: Distribution, + StandardUniform: Distribution, OpenClosed01: Distribution, { s_minus_1: F, @@ -96,7 +96,7 @@ impl std::error::Error for Error {} impl Zeta where F: Float, - Standard: Distribution, + StandardUniform: Distribution, OpenClosed01: Distribution, { /// Construct a new `Zeta` distribution with given `s` parameter. @@ -117,7 +117,7 @@ where impl Distribution for Zeta where F: Float, - Standard: Distribution, + StandardUniform: Distribution, OpenClosed01: Distribution, { #[inline] @@ -135,7 +135,7 @@ where let t = (F::one() + F::one() / x).powf(self.s_minus_1); - let v = rng.sample(Standard); + let v = rng.sample(StandardUniform); if v * x * (t - F::one()) * self.b <= t * (self.b - F::one()) { return x; } diff --git a/rand_distr/src/zipf.rs b/rand_distr/src/zipf.rs index 45a8bf2b5cb..f2e80d37908 100644 --- a/rand_distr/src/zipf.rs +++ b/rand_distr/src/zipf.rs @@ -8,7 +8,7 @@ //! The Zipf distribution. -use crate::{Distribution, Standard}; +use crate::{Distribution, StandardUniform}; use core::fmt; use num_traits::Float; use rand::Rng; @@ -56,7 +56,7 @@ use rand::Rng; pub struct Zipf where F: Float, - Standard: Distribution, + StandardUniform: Distribution, { s: F, t: F, @@ -87,7 +87,7 @@ impl std::error::Error for Error {} impl Zipf where F: Float, - Standard: Distribution, + StandardUniform: Distribution, { /// Construct a new `Zipf` distribution for a set with `n` elements and a /// frequency rank exponent `s`. @@ -137,20 +137,20 @@ where impl Distribution for Zipf where F: Float, - Standard: Distribution, + StandardUniform: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> F { let one = F::one(); loop { - let inv_b = self.inv_cdf(rng.sample(Standard)); + let inv_b = self.inv_cdf(rng.sample(StandardUniform)); let x = (inv_b + one).floor(); let mut ratio = x.powf(-self.s); if x > one { ratio = ratio * inv_b.powf(self.s) }; - let y = rng.sample(Standard); + let y = rng.sample(StandardUniform); if y < ratio { return x; } diff --git a/src/distr/distribution.rs b/src/distr/distribution.rs index cab4ce626fd..f9385ec6172 100644 --- a/src/distr/distribution.rs +++ b/src/distr/distribution.rs @@ -48,12 +48,12 @@ pub trait Distribution { /// # Example /// /// ``` - /// use rand::distr::{Distribution, Alphanumeric, Uniform, Standard}; + /// use rand::distr::{Distribution, Alphanumeric, Uniform, StandardUniform}; /// /// let mut rng = rand::rng(); /// /// // Vec of 16 x f32: - /// let v: Vec = Standard.sample_iter(&mut rng).take(16).collect(); + /// let v: Vec = StandardUniform.sample_iter(&mut rng).take(16).collect(); /// /// // String: /// let s: String = Alphanumeric @@ -246,7 +246,7 @@ mod tests { #[test] #[cfg(feature = "alloc")] fn test_dist_string() { - use crate::distr::{Alphanumeric, DistString, Standard}; + use crate::distr::{Alphanumeric, DistString, StandardUniform}; use core::str; let mut rng = crate::test::rng(213); @@ -254,7 +254,7 @@ mod tests { assert_eq!(s1.len(), 20); assert_eq!(str::from_utf8(s1.as_bytes()), Ok(s1.as_str())); - let s2 = Standard.sample_string(&mut rng, 20); + let s2 = StandardUniform.sample_string(&mut rng, 20); assert_eq!(s2.chars().count(), 20); assert_eq!(str::from_utf8(s2.as_bytes()), Ok(s2.as_str())); } diff --git a/src/distr/float.rs b/src/distr/float.rs index 11d02f76f0e..ec380b4bd4d 100644 --- a/src/distr/float.rs +++ b/src/distr/float.rs @@ -9,7 +9,7 @@ //! Basic floating-point number distributions use crate::distr::utils::{FloatAsSIMD, FloatSIMDUtils, IntAsSIMD}; -use crate::distr::{Distribution, Standard}; +use crate::distr::{Distribution, StandardUniform}; use crate::Rng; use core::mem; #[cfg(feature = "simd_support")] @@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize}; /// 53 most significant bits of a `u64` are used. The conversion uses the /// multiplicative method. /// -/// See also: [`Standard`] which samples from `[0, 1)`, [`Open01`] +/// See also: [`StandardUniform`] which samples from `[0, 1)`, [`Open01`] /// which samples from `(0, 1)` and [`Uniform`] which samples from arbitrary /// ranges. /// @@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize}; /// println!("f32 from (0, 1): {}", val); /// ``` /// -/// [`Standard`]: crate::distr::Standard +/// [`StandardUniform`]: crate::distr::StandardUniform /// [`Open01`]: crate::distr::Open01 /// [`Uniform`]: crate::distr::uniform::Uniform #[derive(Clone, Copy, Debug, Default)] @@ -53,7 +53,7 @@ pub struct OpenClosed01; /// the 23 most significant random bits of an `u32` are used, for `f64` 52 from /// an `u64`. The conversion uses a transmute-based method. /// -/// See also: [`Standard`] which samples from `[0, 1)`, [`OpenClosed01`] +/// See also: [`StandardUniform`] which samples from `[0, 1)`, [`OpenClosed01`] /// which samples from `(0, 1]` and [`Uniform`] which samples from arbitrary /// ranges. /// @@ -66,7 +66,7 @@ pub struct OpenClosed01; /// println!("f32 from (0, 1): {}", val); /// ``` /// -/// [`Standard`]: crate::distr::Standard +/// [`StandardUniform`]: crate::distr::StandardUniform /// [`OpenClosed01`]: crate::distr::OpenClosed01 /// [`Uniform`]: crate::distr::uniform::Uniform #[derive(Clone, Copy, Debug, Default)] @@ -105,7 +105,7 @@ macro_rules! float_impls { } $(#[cfg($meta)])? - impl Distribution<$ty> for Standard { + impl Distribution<$ty> for StandardUniform { fn sample(&self, rng: &mut R) -> $ty { // Multiply-based method; 24/53 random bits; [0, 1) interval. // We use the most significant bits because for simple RNGs @@ -186,7 +186,7 @@ mod tests { fn $fnn() { let two = $ty::splat(2.0); - // Standard + // StandardUniform let mut zeros = StepRng::new(0, 0); assert_eq!(zeros.random::<$ty>(), $ZERO); let mut one = StepRng::new(1 << 8 | 1 << (8 + 32), 0); @@ -234,7 +234,7 @@ mod tests { fn $fnn() { let two = $ty::splat(2.0); - // Standard + // StandardUniform let mut zeros = StepRng::new(0, 0); assert_eq!(zeros.random::<$ty>(), $ZERO); let mut one = StepRng::new(1 << 11, 0); @@ -289,9 +289,13 @@ mod tests { assert_eq!(&buf, expected); } - test_samples(&Standard, 0f32, &[0.0035963655, 0.7346052, 0.09778172]); test_samples( - &Standard, + &StandardUniform, + 0f32, + &[0.0035963655, 0.7346052, 0.09778172], + ); + test_samples( + &StandardUniform, 0f64, &[0.7346051961657583, 0.20298547462974248, 0.8166436635290655], ); @@ -317,7 +321,7 @@ mod tests { // SIMD types. test_samples( - &Standard, + &StandardUniform, f32x2::from([0.0, 0.0]), &[ f32x2::from([0.0035963655, 0.7346052]), @@ -327,7 +331,7 @@ mod tests { ); test_samples( - &Standard, + &StandardUniform, f64x2::from([0.0, 0.0]), &[ f64x2::from([0.7346051961657583, 0.20298547462974248]), diff --git a/src/distr/integer.rs b/src/distr/integer.rs index 8cf9ffc6886..f20f34e0eb7 100644 --- a/src/distr/integer.rs +++ b/src/distr/integer.rs @@ -6,9 +6,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The implementations of the `Standard` distribution for integer types. +//! The implementations of the `StandardUniform` distribution for integer types. -use crate::distr::{Distribution, Standard}; +use crate::distr::{Distribution, StandardUniform}; use crate::Rng; #[cfg(all(target_arch = "x86", feature = "simd_support"))] use core::arch::x86::__m512i; @@ -25,35 +25,35 @@ use core::num::{ #[cfg(feature = "simd_support")] use core::simd::*; -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> u8 { rng.next_u32() as u8 } } -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> u16 { rng.next_u32() as u16 } } -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> u32 { rng.next_u32() } } -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> u64 { rng.next_u64() } } -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> u128 { // Use LE; we explicitly generate one value before the next. @@ -65,7 +65,7 @@ impl Distribution for Standard { macro_rules! impl_int_from_uint { ($ty:ty, $uty:ty) => { - impl Distribution<$ty> for Standard { + impl Distribution<$ty> for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> $ty { rng.random::<$uty>() as $ty @@ -82,7 +82,7 @@ impl_int_from_uint! { i128, u128 } macro_rules! impl_nzint { ($ty:ty, $new:path) => { - impl Distribution<$ty> for Standard { + impl Distribution<$ty> for StandardUniform { fn sample(&self, rng: &mut R) -> $ty { loop { if let Some(nz) = $new(rng.random()) { @@ -110,7 +110,7 @@ impl_nzint!(NonZeroI128, NonZeroI128::new); macro_rules! x86_intrinsic_impl { ($meta:meta, $($intrinsic:ident),+) => {$( #[cfg($meta)] - impl Distribution<$intrinsic> for Standard { + impl Distribution<$intrinsic> for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> $intrinsic { // On proper hardware, this should compile to SIMD instructions @@ -131,7 +131,7 @@ macro_rules! simd_impl { /// /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[cfg(feature = "simd_support")] - impl Distribution> for Standard + impl Distribution> for StandardUniform where LaneCount: SupportedLaneCount, { @@ -174,29 +174,29 @@ mod tests { fn test_integers() { let mut rng = crate::test::rng(806); - rng.sample::(Standard); - rng.sample::(Standard); - rng.sample::(Standard); - rng.sample::(Standard); - rng.sample::(Standard); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); - rng.sample::(Standard); - rng.sample::(Standard); - rng.sample::(Standard); - rng.sample::(Standard); - rng.sample::(Standard); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); } #[test] fn value_stability() { fn test_samples(zero: T, expected: &[T]) where - Standard: Distribution, + StandardUniform: Distribution, { let mut rng = crate::test::rng(807); let mut buf = [zero; 3]; for x in &mut buf { - *x = rng.sample(Standard); + *x = rng.sample(StandardUniform); } assert_eq!(&buf, expected); } diff --git a/src/distr/mod.rs b/src/distr/mod.rs index a1b2802c940..9ee932417fc 100644 --- a/src/distr/mod.rs +++ b/src/distr/mod.rs @@ -28,50 +28,41 @@ //! [`Uniform`] allows specification of its sample space as a range within `T`). //! //! -//! # The `Standard` distribution +//! # The Standard Uniform distribution //! -//! The [`Standard`] distribution is important to mention. This is the +//! The [`StandardUniform`] distribution is important to mention. This is the //! distribution used by [`Rng::random`] and represents the "default" way to //! produce a random value for many different types, including most primitive //! types, tuples, arrays, and a few derived types. See the documentation of -//! [`Standard`] for more details. +//! [`StandardUniform`] for more details. //! -//! Implementing `Distribution` for [`Standard`] for user types `T` makes it +//! Implementing [`Distribution`] for [`StandardUniform`] for user types `T` makes it //! possible to generate type `T` with [`Rng::random`], and by extension also //! with the [`random`] function. //! -//! ## Random characters +//! ## Other standard uniform distributions //! //! [`Alphanumeric`] is a simple distribution to sample random letters and -//! numbers of the `char` type; in contrast [`Standard`] may sample any valid +//! numbers of the `char` type; in contrast [`StandardUniform`] may sample any valid //! `char`. //! +//! For floats (`f32`, `f64`), [`StandardUniform`] samples from `[0, 1)`. Also +//! provided are [`Open01`] (samples from `(0, 1)`) and [`OpenClosed01`] +//! (samples from `(0, 1]`). No option is provided to sample from `[0, 1]`; it +//! is suggested to use one of the above half-open ranges since the failure to +//! sample a value which would have a low chance of being sampled anyway is +//! rarely an issue in practice. //! -//! # Uniform numeric ranges +//! # Parameterized Uniform distributions //! -//! The [`Uniform`] distribution is more flexible than [`Standard`], but also -//! more specialised: it supports fewer target types, but allows the sample -//! space to be specified as an arbitrary range within its target type `T`. -//! Both [`Standard`] and [`Uniform`] are in some sense uniform distributions. +//! The [`Uniform`] distribution provides uniform sampling over a specified +//! range on a subset of the types supported by the above distributions. //! -//! Values may be sampled from this distribution using [`Rng::sample(Range)`] or -//! by creating a distribution object with [`Uniform::new`], -//! [`Uniform::new_inclusive`] or `From`. When the range limits are not -//! known at compile time it is typically faster to reuse an existing -//! `Uniform` object than to call [`Rng::sample(Range)`]. -//! -//! User types `T` may also implement `Distribution` for [`Uniform`], -//! although this is less straightforward than for [`Standard`] (see the -//! documentation in the [`uniform`] module). Doing so enables generation of -//! values of type `T` with [`Rng::sample(Range)`]. -//! -//! ## Open and half-open ranges -//! -//! There are surprisingly many ways to uniformly generate random floats. A -//! range between 0 and 1 is standard, but the exact bounds (open vs closed) -//! and accuracy differ. In addition to the [`Standard`] distribution Rand offers -//! [`Open01`] and [`OpenClosed01`]. See "Floating point implementation" section of -//! [`Standard`] documentation for more details. +//! Implementations support single-value-sampling via +//! [`Rng::random_range(Range)`](Rng::random_range). +//! Where a fixed (non-`const`) range will be sampled many times, it is likely +//! faster to pre-construct a [`Distribution`] object using +//! [`Uniform::new`], [`Uniform::new_inclusive`] or `From`. //! //! # Non-uniform sampling //! @@ -124,61 +115,62 @@ pub use self::weighted_index::{Weight, WeightError, WeightedIndex}; #[allow(unused)] use crate::Rng; -/// A generic random value distribution, implemented for many primitive types. -/// Usually generates values with a numerically uniform distribution, and with a -/// range appropriate to the type. +/// The Standard Uniform distribution /// -/// ## Provided implementations +/// This [`Distribution`] is the *standard* parameterization of [`Uniform`]. Bounds +/// are selected according to the output type. /// /// Assuming the provided `Rng` is well-behaved, these implementations /// generate values with the following ranges and distributions: /// -/// * Integers (`i32`, `u32`, `isize`, `usize`, etc.): Uniformly distributed -/// over all values of the type. -/// * `char`: Uniformly distributed over all Unicode scalar values, i.e. all +/// * Integers (`i8`, `i32`, `u64`, etc.) are uniformly distributed +/// over the whole range of the type (thus each possible value may be sampled +/// with equal probability). +/// * `char` is uniformly distributed over all Unicode scalar values, i.e. all /// code points in the range `0...0x10_FFFF`, except for the range /// `0xD800...0xDFFF` (the surrogate code points). This includes /// unassigned/reserved code points. -/// * `bool`: Generates `false` or `true`, each with probability 0.5. -/// * Floating point types (`f32` and `f64`): Uniformly distributed in the -/// half-open range `[0, 1)`. See notes below. +/// For some uses, the [`Alphanumeric`] distribution will be more appropriate. +/// * `bool` samples `false` or `true`, each with probability 0.5. +/// * Floating point types (`f32` and `f64`) are uniformly distributed in the +/// half-open range `[0, 1)`. See also the [notes below](#floating-point-implementation). /// * Wrapping integers ([`Wrapping`]), besides the type identical to their /// normal integer variants. /// * Non-zero integers ([`NonZeroU8`]), which are like their normal integer -/// variants but cannot produce zero. -/// * SIMD types like x86's [`__m128i`], `std::simd`'s [`u32x4`]/[`f32x4`]/ -/// [`mask32x4`] (requires [`simd_support`]), where each lane is distributed -/// like their scalar `Standard` variants. See the list of `Standard` -/// implementations for more. +/// variants but cannot sample zero. /// -/// The `Standard` distribution also supports generation of the following +/// The `StandardUniform` distribution also supports generation of the following /// compound types where all component types are supported: /// -/// * Tuples (up to 12 elements): each element is generated sequentially. -/// * Arrays: each element is generated sequentially; -/// see also [`Rng::fill`] which supports arbitrary array length for integer -/// and float types and tends to be faster for `u32` and smaller types. -/// Note that [`Rng::fill`] and `Standard`'s array support are *not* equivalent: -/// the former is optimised for integer types (using fewer RNG calls for -/// element types smaller than the RNG word size), while the latter supports -/// any element type supported by `Standard`. -/// * `Option` first generates a `bool`, and if true generates and returns -/// `Some(value)` where `value: T`, otherwise returning `None`. +/// * Tuples (up to 12 elements): each element is sampled sequentially and +/// independently (thus, assuming a well-behaved RNG, there is no correlation +/// between elements). +/// * Arrays `[T; n]` where `T` is supported. Each element is sampled +/// sequentially and independently. Note that for small `T` this usually +/// results in the RNG discarding random bits; see also [`Rng::fill`] which +/// offers a more efficient approach to filling an array of integer types +/// with random data. +/// * SIMD types (requires [`simd_support`] feature) like x86's [`__m128i`] +/// and `std::simd`'s [`u32x4`], [`f32x4`] and [`mask32x4`] types are +/// effectively arrays of integer or floating-point types. Each lane is +/// sampled independently, potentially with more efficient random-bit-usage +/// (and a different resulting value) than would be achieved with sequential +/// sampling (as with the array types above). /// /// ## Custom implementations /// -/// The [`Standard`] distribution may be implemented for user types as follows: +/// The [`StandardUniform`] distribution may be implemented for user types as follows: /// /// ``` /// # #![allow(dead_code)] /// use rand::Rng; -/// use rand::distr::{Distribution, Standard}; +/// use rand::distr::{Distribution, StandardUniform}; /// /// struct MyF32 { /// x: f32, /// } /// -/// impl Distribution for Standard { +/// impl Distribution for StandardUniform { /// fn sample(&self, rng: &mut R) -> MyF32 { /// MyF32 { x: rng.random() } /// } @@ -188,14 +180,14 @@ use crate::Rng; /// ## Example usage /// ``` /// use rand::prelude::*; -/// use rand::distr::Standard; +/// use rand::distr::StandardUniform; /// -/// let val: f32 = StdRng::from_os_rng().sample(Standard); +/// let val: f32 = StdRng::from_os_rng().sample(StandardUniform); /// println!("f32 from [0, 1): {}", val); /// ``` /// /// # Floating point implementation -/// The floating point implementations for `Standard` generate a random value in +/// The floating point implementations for `StandardUniform` generate a random value in /// the half-open interval `[0, 1)`, i.e. including 0 but not 1. /// /// All values that can be generated are of the form `n * ε/2`. For `f32` @@ -219,4 +211,4 @@ use crate::Rng; /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Standard; +pub struct StandardUniform; diff --git a/src/distr/other.rs b/src/distr/other.rs index f350731477b..8e957f07446 100644 --- a/src/distr/other.rs +++ b/src/distr/other.rs @@ -6,7 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! The implementations of the `Standard` distribution for other built-in types. +//! The implementations of the `StandardUniform` distribution for other built-in types. #[cfg(feature = "alloc")] use alloc::string::String; @@ -15,7 +15,7 @@ use core::num::Wrapping; #[cfg(feature = "alloc")] use crate::distr::DistString; -use crate::distr::{Distribution, Standard, Uniform}; +use crate::distr::{Distribution, StandardUniform, Uniform}; use crate::Rng; use core::mem::{self, MaybeUninit}; @@ -72,7 +72,7 @@ pub struct Alphanumeric; // ----- Implementations of distributions ----- -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> char { // A valid `char` is either in the interval `[0, 0xD800)` or @@ -96,7 +96,7 @@ impl Distribution for Standard { /// Note: the `String` is potentially left with excess capacity; optionally the /// user may call `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -impl DistString for Standard { +impl DistString for StandardUniform { fn append_string(&self, rng: &mut R, s: &mut String, len: usize) { // A char is encoded with at most four bytes, thus this reservation is // guaranteed to be sufficient. We do not shrink_to_fit afterwards so @@ -135,7 +135,7 @@ impl DistString for Alphanumeric { } } -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> bool { // We can compare against an arbitrary bit of an u32 to get a bool. @@ -173,11 +173,11 @@ impl Distribution for Standard { /// [`_mm_blendv_epi8`]: https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8&ig_expand=514/ /// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[cfg(feature = "simd_support")] -impl Distribution> for Standard +impl Distribution> for StandardUniform where T: MaskElement + Default, LaneCount: SupportedLaneCount, - Standard: Distribution>, + StandardUniform: Distribution>, Simd: SimdPartialOrd>, { #[inline] @@ -189,13 +189,13 @@ where } } -/// Implement `Distribution<(A, B, C, ...)> for Standard`, using the list of +/// Implement `Distribution<(A, B, C, ...)> for StandardUniform`, using the list of /// identifiers macro_rules! tuple_impl { ($($tyvar:ident)*) => { - impl< $($tyvar,)* > Distribution<($($tyvar,)*)> for Standard + impl< $($tyvar,)* > Distribution<($($tyvar,)*)> for StandardUniform where $( - Standard: Distribution< $tyvar >, + StandardUniform: Distribution< $tyvar >, )* { #[inline] @@ -234,9 +234,9 @@ macro_rules! tuple_impls { tuple_impls! {A B C D E F G H I J K L} -impl Distribution<[T; N]> for Standard +impl Distribution<[T; N]> for StandardUniform where - Standard: Distribution, + StandardUniform: Distribution, { #[inline] fn sample(&self, _rng: &mut R) -> [T; N] { @@ -250,24 +250,9 @@ where } } -impl Distribution> for Standard +impl Distribution> for StandardUniform where - Standard: Distribution, -{ - #[inline] - fn sample(&self, rng: &mut R) -> Option { - // UFCS is needed here: https://github.com/rust-lang/rust/issues/24066 - if rng.random::() { - Some(rng.random()) - } else { - None - } - } -} - -impl Distribution> for Standard -where - Standard: Distribution, + StandardUniform: Distribution, { #[inline] fn sample(&self, rng: &mut R) -> Wrapping { @@ -284,8 +269,8 @@ mod tests { fn test_misc() { let rng: &mut dyn RngCore = &mut crate::test::rng(820); - rng.sample::(Standard); - rng.sample::(Standard); + rng.sample::(StandardUniform); + rng.sample::(StandardUniform); } #[cfg(feature = "alloc")] @@ -333,7 +318,7 @@ mod tests { } test_samples( - &Standard, + &StandardUniform, 'a', &[ '\u{8cdac}', @@ -344,14 +329,9 @@ mod tests { ], ); test_samples(&Alphanumeric, 0, &[104, 109, 101, 51, 77]); - test_samples(&Standard, false, &[true, true, false, true, false]); - test_samples( - &Standard, - None as Option, - &[Some(true), None, Some(false), None, Some(false)], - ); + test_samples(&StandardUniform, false, &[true, true, false, true, false]); test_samples( - &Standard, + &StandardUniform, Wrapping(0i32), &[ Wrapping(-2074640887), @@ -363,14 +343,14 @@ mod tests { ); // We test only sub-sets of tuple and array impls - test_samples(&Standard, (), &[(), (), (), (), ()]); + test_samples(&StandardUniform, (), &[(), (), (), (), ()]); test_samples( - &Standard, + &StandardUniform, (false,), &[(true,), (true,), (false,), (true,), (false,)], ); test_samples( - &Standard, + &StandardUniform, (false, false), &[ (true, true), @@ -381,9 +361,9 @@ mod tests { ], ); - test_samples(&Standard, [0u8; 0], &[[], [], [], [], []]); + test_samples(&StandardUniform, [0u8; 0], &[[], [], [], [], []]); test_samples( - &Standard, + &StandardUniform, [0u8; 3], &[ [9, 247, 111], diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index ac3e1676ada..b59fdbf790b 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -154,7 +154,7 @@ use serde::{Deserialize, Serialize}; /// When sampling from a constant range, many calculations can happen at /// compile-time and all methods should be fast; for floating-point ranges and /// the full range of integer types, this should have comparable performance to -/// the `Standard` distribution. +/// the [`StandardUniform`](super::StandardUniform) distribution. /// /// # Provided implementations /// @@ -173,6 +173,11 @@ use serde::{Deserialize, Serialize}; /// 64-bit CPU architectures. /// - `Duration` ([`UniformDuration`]): samples a range over the implementation /// for `u32` or `u64` +/// - SIMD types (requires [`simd_support`] feature) like x86's [`__m128i`] +/// and `std::simd`'s [`u32x4`], [`f32x4`] and [`mask32x4`] types are +/// effectively arrays of integer or floating-point types. Each lane is +/// sampled independently from its own range, potentially with more efficient +/// random-bit-usage than would be achieved with sequential sampling. /// /// # Example /// @@ -200,6 +205,11 @@ use serde::{Deserialize, Serialize}; /// [`new`]: Uniform::new /// [`new_inclusive`]: Uniform::new_inclusive /// [`Rng::random_range`]: Rng::random_range +/// [`__m128i`]: https://doc.rust-lang.org/core/arch/x86/struct.__m128i.html +/// [`u32x4`]: std::simd::u32x4 +/// [`f32x4`]: std::simd::f32x4 +/// [`mask32x4`]: std::simd::mask32x4 +/// [`simd_support`]: https://github.com/rust-random/rand#crate-features #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(bound(serialize = "X::Sampler: Serialize")))] diff --git a/src/distr/uniform_float.rs b/src/distr/uniform_float.rs index 99bf2bf6011..adcc7b710d6 100644 --- a/src/distr/uniform_float.rs +++ b/src/distr/uniform_float.rs @@ -43,7 +43,7 @@ use serde::{Deserialize, Serialize}; /// /// [`new`]: UniformSampler::new /// [`new_inclusive`]: UniformSampler::new_inclusive -/// [`Standard`]: crate::distr::Standard +/// [`StandardUniform`]: crate::distr::StandardUniform /// [`Uniform`]: super::Uniform #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/distr/uniform_int.rs b/src/distr/uniform_int.rs index 4fe07707bd3..d5c56b02a0b 100644 --- a/src/distr/uniform_int.rs +++ b/src/distr/uniform_int.rs @@ -12,7 +12,7 @@ use super::{Error, SampleBorrow, SampleUniform, UniformSampler}; use crate::distr::utils::WideningMultiply; #[cfg(feature = "simd_support")] -use crate::distr::{Distribution, Standard}; +use crate::distr::{Distribution, StandardUniform}; use crate::Rng; #[cfg(feature = "simd_support")] @@ -286,7 +286,7 @@ macro_rules! uniform_simd_int_impl { LaneCount: SupportedLaneCount, Simd<$unsigned, LANES>: WideningMultiply, Simd<$unsigned, LANES>)>, - Standard: Distribution>, + StandardUniform: Distribution>, { type Sampler = UniformInt>; } @@ -297,7 +297,7 @@ macro_rules! uniform_simd_int_impl { LaneCount: SupportedLaneCount, Simd<$unsigned, LANES>: WideningMultiply, Simd<$unsigned, LANES>)>, - Standard: Distribution>, + StandardUniform: Distribution>, { type X = Simd<$ty, LANES>; diff --git a/src/lib.rs b/src/lib.rs index f9092925471..5b410ca8cb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,14 +119,14 @@ pub fn thread_rng() -> crate::rngs::ThreadRng { pub use rng::{Fill, Rng}; #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] -use crate::distr::{Distribution, Standard}; +use crate::distr::{Distribution, StandardUniform}; /// Generate a random value using the thread-local random number generator. /// /// This function is shorthand for [rng()].[random()](Rng::random): /// /// - See [`ThreadRng`] for documentation of the generator and security -/// - See [`Standard`] for documentation of supported types and distributions +/// - See [`StandardUniform`] for documentation of supported types and distributions /// /// # Examples /// @@ -157,13 +157,13 @@ use crate::distr::{Distribution, Standard}; /// } /// ``` /// -/// [`Standard`]: distr::Standard +/// [`StandardUniform`]: distr::StandardUniform /// [`ThreadRng`]: rngs::ThreadRng #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] #[inline] pub fn random() -> T where - Standard: Distribution, + StandardUniform: Distribution, { rng().random() } @@ -181,9 +181,9 @@ where /// ``` #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] #[inline] -pub fn random_iter() -> distr::DistIter +pub fn random_iter() -> distr::DistIter where - Standard: Distribution, + StandardUniform: Distribution, { rng().random_iter() } @@ -306,11 +306,10 @@ mod test { fn test_random() { let _n: u64 = random(); let _f: f32 = random(); - let _o: Option> = random(); #[allow(clippy::type_complexity)] let _many: ( (), - Option<(u32, (bool,))>, + [(u32, bool); 3], (u8, i8, u16, i16, u32, i32, u64, i64), (f32, (f64, (f64,))), ) = random(); diff --git a/src/rng.rs b/src/rng.rs index 9ac481ed9ce..04b71f74b79 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -10,7 +10,7 @@ //! [`Rng`] trait use crate::distr::uniform::{SampleRange, SampleUniform}; -use crate::distr::{self, Distribution, Standard}; +use crate::distr::{self, Distribution, StandardUniform}; use core::num::Wrapping; use rand_core::RngCore; use zerocopy::IntoBytes; @@ -56,7 +56,7 @@ use zerocopy::IntoBytes; /// # let v = foo(&mut rand::rng()); /// ``` pub trait Rng: RngCore { - /// Return a random value via the [`Standard`] distribution. + /// Return a random value via the [`StandardUniform`] distribution. /// /// # Example /// @@ -90,19 +90,19 @@ pub trait Rng: RngCore { /// rng.fill(&mut arr2); // array fill /// ``` /// - /// [`Standard`]: distr::Standard + /// [`StandardUniform`]: distr::StandardUniform #[inline] fn random(&mut self) -> T where - Standard: Distribution, + StandardUniform: Distribution, { - Standard.sample(self) + StandardUniform.sample(self) } /// Return an iterator over [`random`](Self::random) variates /// /// This is a just a wrapper over [`Rng::sample_iter`] using - /// [`distr::Standard`]. + /// [`distr::StandardUniform`]. /// /// Note: this method consumes its argument. Use /// `(&mut rng).random_iter()` to avoid consuming the RNG. @@ -117,12 +117,12 @@ pub trait Rng: RngCore { /// assert_eq!(&v, &[1, 2, 3, 4, 5]); /// ``` #[inline] - fn random_iter(self) -> distr::DistIter + fn random_iter(self) -> distr::DistIter where Self: Sized, - Standard: Distribution, + StandardUniform: Distribution, { - Standard.sample_iter(self) + StandardUniform.sample_iter(self) } /// Generate a random value in the given range. @@ -259,12 +259,12 @@ pub trait Rng: RngCore { /// /// ``` /// use rand::Rng; - /// use rand::distr::{Alphanumeric, Uniform, Standard}; + /// use rand::distr::{Alphanumeric, Uniform, StandardUniform}; /// /// let mut rng = rand::rng(); /// /// // Vec of 16 x f32: - /// let v: Vec = (&mut rng).sample_iter(Standard).take(16).collect(); + /// let v: Vec = (&mut rng).sample_iter(StandardUniform).take(16).collect(); /// /// // String: /// let s: String = (&mut rng).sample_iter(Alphanumeric) @@ -273,7 +273,7 @@ pub trait Rng: RngCore { /// .collect(); /// /// // Combined values - /// println!("{:?}", (&mut rng).sample_iter(Standard).take(5) + /// println!("{:?}", (&mut rng).sample_iter(StandardUniform).take(5) /// .collect::>()); /// /// // Dice-rolling: @@ -323,7 +323,7 @@ pub trait Rng: RngCore { )] fn r#gen(&mut self) -> T where - Standard: Distribution, + StandardUniform: Distribution, { self.random() } @@ -580,25 +580,25 @@ mod test { #[test] fn test_rng_trait_object() { - use crate::distr::{Distribution, Standard}; + use crate::distr::{Distribution, StandardUniform}; let mut rng = rng(109); let mut r = &mut rng as &mut dyn RngCore; r.next_u32(); r.random::(); assert_eq!(r.random_range(0..1), 0); - let _c: u8 = Standard.sample(&mut r); + let _c: u8 = StandardUniform.sample(&mut r); } #[test] #[cfg(feature = "alloc")] fn test_rng_boxed_trait() { - use crate::distr::{Distribution, Standard}; + use crate::distr::{Distribution, StandardUniform}; let rng = rng(110); let mut r = Box::new(rng) as Box; r.next_u32(); r.random::(); assert_eq!(r.random_range(0..1), 0); - let _c: u8 = Standard.sample(&mut r); + let _c: u8 = StandardUniform.sample(&mut r); } #[test] diff --git a/src/rngs/mock.rs b/src/rngs/mock.rs index dc0bd764a6b..b6da66a8565 100644 --- a/src/rngs/mock.rs +++ b/src/rngs/mock.rs @@ -93,11 +93,11 @@ mod tests { #[test] #[cfg(feature = "alloc")] fn test_bool() { - use crate::{distr::Standard, Rng}; + use crate::{distr::StandardUniform, Rng}; // If this result ever changes, update doc on StepRng! let rng = StepRng::new(0, 1 << 31); - let result: alloc::vec::Vec = rng.sample_iter(Standard).take(6).collect(); + let result: alloc::vec::Vec = rng.sample_iter(StandardUniform).take(6).collect(); assert_eq!(&result, &[false, true, false, true, false, true]); } } From 935a3e3af05140f3246e17265bf67f9b6c698f2e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 21 Nov 2024 10:33:59 +0000 Subject: [PATCH 428/443] Remove rng parameter of ReseedingRng::new (#1533) The `rng` parameter seems redundant in `ReseedingRng::new` (aside from the type specification). --- CHANGELOG.md | 1 + benches/benches/generators.rs | 2 +- src/rngs/reseeding.rs | 34 +++++++++++++++++++--------------- src/rngs/thread.rs | 8 +++----- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44603eeb537..32919ded611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `Rng::gen_range` to `random_range`, `gen_bool` to `random_bool`, `gen_ratio` to `random_ratio` (#1505) - Rename `Standard` to `StandardUniform` (#1526) - Remove impl of `Distribution>` for `Standard` (#1526) +- Remove first parameter (`rng`) of `ReseedingRng::new` (#1533) ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index 95361cd203f..5c98d37ef65 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -201,7 +201,7 @@ pub fn reseeding_bytes(c: &mut Criterion) { fn bench(g: &mut BenchmarkGroup, thresh: u64) { let name = format!("chacha20_{}k", thresh); g.bench_function(name.as_str(), |b| { - let mut rng = ReseedingRng::new(ChaCha20Core::from_os_rng(), thresh * 1024, OsRng); + let mut rng = ReseedingRng::::new(thresh * 1024, OsRng).unwrap(); let mut buf = [0u8; 1024 * 1024]; b.iter(|| { rng.fill_bytes(&mut buf); diff --git a/src/rngs/reseeding.rs b/src/rngs/reseeding.rs index a0076f959a6..570d04eeba4 100644 --- a/src/rngs/reseeding.rs +++ b/src/rngs/reseeding.rs @@ -59,8 +59,7 @@ use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore}; /// use rand::rngs::OsRng; /// use rand::rngs::ReseedingRng; /// -/// let prng = ChaCha20Core::from_os_rng(); -/// let mut reseeding_rng = ReseedingRng::new(prng, 0, OsRng); +/// let mut reseeding_rng = ReseedingRng::::new(0, OsRng).unwrap(); /// /// println!("{}", reseeding_rng.random::()); /// @@ -88,8 +87,10 @@ where /// `threshold` sets the number of generated bytes after which to reseed the /// PRNG. Set it to zero to never reseed based on the number of generated /// values. - pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> Self { - ReseedingRng(BlockRng::new(ReseedingCore::new(rng, threshold, reseeder))) + pub fn new(threshold: u64, reseeder: Rsdr) -> Result { + Ok(ReseedingRng(BlockRng::new(ReseedingCore::new( + threshold, reseeder, + )?))) } /// Immediately reseed the generator @@ -177,7 +178,10 @@ where Rsdr: TryRngCore, { /// Create a new `ReseedingCore`. - fn new(rng: R, threshold: u64, reseeder: Rsdr) -> Self { + /// + /// `threshold` is the maximum number of bytes produced by + /// [`BlockRngCore::generate`] before attempting reseeding. + fn new(threshold: u64, mut reseeder: Rsdr) -> Result { // Because generating more values than `i64::MAX` takes centuries on // current hardware, we just clamp to that value. // Also we set a threshold of 0, which indicates no limit, to that @@ -190,12 +194,14 @@ where i64::MAX }; - ReseedingCore { - inner: rng, + let inner = R::try_from_rng(&mut reseeder)?; + + Ok(ReseedingCore { + inner, reseeder, threshold, bytes_until_reseed: threshold, - } + }) } /// Reseed the internal PRNG. @@ -249,16 +255,15 @@ where mod test { use crate::rngs::mock::StepRng; use crate::rngs::std::Core; - use crate::{Rng, SeedableRng}; + use crate::Rng; use super::ReseedingRng; #[test] fn test_reseeding() { - let mut zero = StepRng::new(0, 0); - let rng = Core::from_rng(&mut zero); + let zero = StepRng::new(0, 0); let thresh = 1; // reseed every time the buffer is exhausted - let mut reseeding = ReseedingRng::new(rng, thresh, zero); + let mut reseeding = ReseedingRng::::new(thresh, zero).unwrap(); // RNG buffer size is [u32; 64] // Debug is only implemented up to length 32 so use two arrays @@ -276,9 +281,8 @@ mod test { #[test] #[allow(clippy::redundant_clone)] fn test_clone_reseeding() { - let mut zero = StepRng::new(0, 0); - let rng = Core::from_rng(&mut zero); - let mut rng1 = ReseedingRng::new(rng, 32 * 4, zero); + let zero = StepRng::new(0, 0); + let mut rng1 = ReseedingRng::::new(32 * 4, zero).unwrap(); let first: u32 = rng1.random(); for _ in 0..10 { diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 64ca0e1f761..38446759ab9 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -13,7 +13,7 @@ use std::fmt; use std::rc::Rc; use std::thread_local; -use rand_core::{CryptoRng, RngCore, SeedableRng}; +use rand_core::{CryptoRng, RngCore}; use super::std::Core; use crate::rngs::OsRng; @@ -119,11 +119,9 @@ thread_local!( // We require Rc<..> to avoid premature freeing when ThreadRng is used // within thread-local destructors. See #968. static THREAD_RNG_KEY: Rc>> = { - let r = Core::try_from_os_rng().unwrap_or_else(|err| + let rng = ReseedingRng::new(THREAD_RNG_RESEED_THRESHOLD, + OsRng).unwrap_or_else(|err| panic!("could not initialize ThreadRng: {}", err)); - let rng = ReseedingRng::new(r, - THREAD_RNG_RESEED_THRESHOLD, - OsRng); Rc::new(UnsafeCell::new(rng)) } ); From 4807e260aac1a3ca6d134bc88de9fe1221eb18aa Mon Sep 17 00:00:00 2001 From: Dan Johnson Date: Fri, 22 Nov 2024 03:10:37 -0800 Subject: [PATCH 429/443] rand_chacha: remove conditional compilation around using core (#1534) --- rand_chacha/src/chacha.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/rand_chacha/src/chacha.rs b/rand_chacha/src/chacha.rs index 831858090d9..91d3cd628d2 100644 --- a/rand_chacha/src/chacha.rs +++ b/rand_chacha/src/chacha.rs @@ -8,13 +8,8 @@ //! The ChaCha random number generator. -#[cfg(not(feature = "std"))] -use core; -#[cfg(feature = "std")] -use std as core; - -use self::core::fmt; use crate::guts::ChaCha; +use core::fmt; use rand_core::block::{BlockRng, BlockRngCore, CryptoBlockRng}; use rand_core::{CryptoRng, RngCore, SeedableRng}; From c68463ce088af24540d572f29c99d2e5ca68e3e8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 22 Nov 2024 11:10:57 +0000 Subject: [PATCH 430/443] Remove SmallRng::from_thread_rng (#1532) --- CHANGELOG.md | 1 + benches/benches/distr.rs | 12 +++---- benches/benches/generators.rs | 60 +++++++++++++++++------------------ benches/benches/standard.rs | 2 +- rand_chacha/src/lib.rs | 3 ++ rand_pcg/src/lib.rs | 22 ++++++------- src/distr/mod.rs | 2 +- src/rngs/small.rs | 40 +++++++++-------------- src/rngs/std.rs | 13 ++++++-- 9 files changed, 77 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32919ded611..56579821c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Rename `Rng::gen_range` to `random_range`, `gen_bool` to `random_bool`, `gen_ratio` to `random_ratio` (#1505) - Rename `Standard` to `StandardUniform` (#1526) - Remove impl of `Distribution>` for `Standard` (#1526) +- Remove `SmallRng::from_thread_rng` (#1532) - Remove first parameter (`rng`) of `ReseedingRng::new` (#1533) ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/benches/benches/distr.rs b/benches/benches/distr.rs index ec43e7e61cc..94a39404437 100644 --- a/benches/benches/distr.rs +++ b/benches/benches/distr.rs @@ -25,7 +25,7 @@ const ITER_ELTS: u64 = 100; macro_rules! distr_int { ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { $group.bench_function($fnn, |c| { - let mut rng = Pcg64Mcg::from_os_rng(); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); let distr = $distr; c.iter(|| distr.sample(&mut rng)); @@ -36,7 +36,7 @@ macro_rules! distr_int { macro_rules! distr_float { ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { $group.bench_function($fnn, |c| { - let mut rng = Pcg64Mcg::from_os_rng(); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); let distr = $distr; c.iter(|| Distribution::<$ty>::sample(&distr, &mut rng)); @@ -47,7 +47,7 @@ macro_rules! distr_float { macro_rules! distr_arr { ($group:ident, $fnn:expr, $ty:ty, $distr:expr) => { $group.bench_function($fnn, |c| { - let mut rng = Pcg64Mcg::from_os_rng(); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); let distr = $distr; c.iter(|| Distribution::<$ty>::sample(&distr, &mut rng)); @@ -76,7 +76,7 @@ fn bench(c: &mut Criterion) { g.throughput(Throughput::Elements(ITER_ELTS)); g.bench_function("iter", |c| { use core::f64::consts::{E, PI}; - let mut rng = Pcg64Mcg::from_os_rng(); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); let distr = Normal::new(-E, PI).unwrap(); c.iter(|| { @@ -145,7 +145,7 @@ fn bench(c: &mut Criterion) { } g.throughput(Throughput::Elements(ITER_ELTS)); g.bench_function("variable", |c| { - let mut rng = Pcg64Mcg::from_os_rng(); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); let ldistr = Uniform::new(0.1, 10.0).unwrap(); c.iter(|| { @@ -165,7 +165,7 @@ fn bench(c: &mut Criterion) { let mut g = c.benchmark_group("bernoulli"); g.bench_function("bernoulli", |c| { - let mut rng = Pcg64Mcg::from_os_rng(); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); let distr = Bernoulli::new(0.18).unwrap(); c.iter(|| distr.sample(&mut rng)) }); diff --git a/benches/benches/generators.rs b/benches/benches/generators.rs index 5c98d37ef65..64325ceb9ee 100644 --- a/benches/benches/generators.rs +++ b/benches/benches/generators.rs @@ -40,15 +40,15 @@ pub fn random_bytes(c: &mut Criterion) { } bench(&mut g, "step", StepRng::new(0, 1)); - bench(&mut g, "pcg32", Pcg32::from_os_rng()); - bench(&mut g, "pcg64", Pcg64::from_os_rng()); - bench(&mut g, "pcg64mcg", Pcg64Mcg::from_os_rng()); - bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_os_rng()); - bench(&mut g, "chacha8", ChaCha8Rng::from_os_rng()); - bench(&mut g, "chacha12", ChaCha12Rng::from_os_rng()); - bench(&mut g, "chacha20", ChaCha20Rng::from_os_rng()); - bench(&mut g, "std", StdRng::from_os_rng()); - bench(&mut g, "small", SmallRng::from_thread_rng()); + bench(&mut g, "pcg32", Pcg32::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64", Pcg64::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64mcg", Pcg64Mcg::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_rng(&mut rand::rng())); + bench(&mut g, "chacha8", ChaCha8Rng::from_rng(&mut rand::rng())); + bench(&mut g, "chacha12", ChaCha12Rng::from_rng(&mut rand::rng())); + bench(&mut g, "chacha20", ChaCha20Rng::from_rng(&mut rand::rng())); + bench(&mut g, "std", StdRng::from_rng(&mut rand::rng())); + bench(&mut g, "small", SmallRng::from_rng(&mut rand::rng())); bench(&mut g, "os", UnwrapErr(OsRng)); bench(&mut g, "thread", rand::rng()); @@ -69,15 +69,15 @@ pub fn random_u32(c: &mut Criterion) { } bench(&mut g, "step", StepRng::new(0, 1)); - bench(&mut g, "pcg32", Pcg32::from_os_rng()); - bench(&mut g, "pcg64", Pcg64::from_os_rng()); - bench(&mut g, "pcg64mcg", Pcg64Mcg::from_os_rng()); - bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_os_rng()); - bench(&mut g, "chacha8", ChaCha8Rng::from_os_rng()); - bench(&mut g, "chacha12", ChaCha12Rng::from_os_rng()); - bench(&mut g, "chacha20", ChaCha20Rng::from_os_rng()); - bench(&mut g, "std", StdRng::from_os_rng()); - bench(&mut g, "small", SmallRng::from_thread_rng()); + bench(&mut g, "pcg32", Pcg32::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64", Pcg64::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64mcg", Pcg64Mcg::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_rng(&mut rand::rng())); + bench(&mut g, "chacha8", ChaCha8Rng::from_rng(&mut rand::rng())); + bench(&mut g, "chacha12", ChaCha12Rng::from_rng(&mut rand::rng())); + bench(&mut g, "chacha20", ChaCha20Rng::from_rng(&mut rand::rng())); + bench(&mut g, "std", StdRng::from_rng(&mut rand::rng())); + bench(&mut g, "small", SmallRng::from_rng(&mut rand::rng())); bench(&mut g, "os", UnwrapErr(OsRng)); bench(&mut g, "thread", rand::rng()); @@ -98,15 +98,15 @@ pub fn random_u64(c: &mut Criterion) { } bench(&mut g, "step", StepRng::new(0, 1)); - bench(&mut g, "pcg32", Pcg32::from_os_rng()); - bench(&mut g, "pcg64", Pcg64::from_os_rng()); - bench(&mut g, "pcg64mcg", Pcg64Mcg::from_os_rng()); - bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_os_rng()); - bench(&mut g, "chacha8", ChaCha8Rng::from_os_rng()); - bench(&mut g, "chacha12", ChaCha12Rng::from_os_rng()); - bench(&mut g, "chacha20", ChaCha20Rng::from_os_rng()); - bench(&mut g, "std", StdRng::from_os_rng()); - bench(&mut g, "small", SmallRng::from_thread_rng()); + bench(&mut g, "pcg32", Pcg32::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64", Pcg64::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64mcg", Pcg64Mcg::from_rng(&mut rand::rng())); + bench(&mut g, "pcg64dxsm", Pcg64Dxsm::from_rng(&mut rand::rng())); + bench(&mut g, "chacha8", ChaCha8Rng::from_rng(&mut rand::rng())); + bench(&mut g, "chacha12", ChaCha12Rng::from_rng(&mut rand::rng())); + bench(&mut g, "chacha20", ChaCha20Rng::from_rng(&mut rand::rng())); + bench(&mut g, "std", StdRng::from_rng(&mut rand::rng())); + bench(&mut g, "small", SmallRng::from_rng(&mut rand::rng())); bench(&mut g, "os", UnwrapErr(OsRng)); bench(&mut g, "thread", rand::rng()); @@ -120,7 +120,7 @@ pub fn init_gen(c: &mut Criterion) { fn bench(g: &mut BenchmarkGroup, name: &str) { g.bench_function(name, |b| { - let mut rng = Pcg32::from_os_rng(); + let mut rng = Pcg32::from_rng(&mut rand::rng()); b.iter(|| R::from_rng(&mut rng)); }); } @@ -145,7 +145,7 @@ pub fn init_from_u64(c: &mut Criterion) { fn bench(g: &mut BenchmarkGroup, name: &str) { g.bench_function(name, |b| { - let mut rng = Pcg32::from_os_rng(); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let seed = rng.random(); b.iter(|| R::seed_from_u64(black_box(seed))); }); @@ -174,7 +174,7 @@ pub fn init_from_seed(c: &mut Criterion) { rand::distr::StandardUniform: Distribution<::Seed>, { g.bench_function(name, |b| { - let mut rng = Pcg32::from_os_rng(); + let mut rng = Pcg32::from_rng(&mut rand::rng()); let seed = rng.random(); b.iter(|| R::from_seed(black_box(seed.clone()))); }); diff --git a/benches/benches/standard.rs b/benches/benches/standard.rs index ffe7ea5de1f..ac38f0225f8 100644 --- a/benches/benches/standard.rs +++ b/benches/benches/standard.rs @@ -27,7 +27,7 @@ where { g.throughput(criterion::Throughput::Bytes(size_of::() as u64)); g.bench_function(name, |b| { - let mut rng = Pcg64Mcg::from_os_rng(); + let mut rng = Pcg64Mcg::from_rng(&mut rand::rng()); b.iter(|| rng.sample::(D::default())); }); diff --git a/rand_chacha/src/lib.rs b/rand_chacha/src/lib.rs index e7a3d25eb1a..24ddd601d27 100644 --- a/rand_chacha/src/lib.rs +++ b/rand_chacha/src/lib.rs @@ -46,6 +46,9 @@ //! or a deterministic generator such as [`ChaCha20Rng`]. //! Beware that should a weak master generator be used, correlations may be //! detectable between the outputs of its child generators. +//! ```ignore +//! let rng = ChaCha12Rng::from_rng(&mut rand::rng()); +//! ``` //! //! See also [Seeding RNGs] in the book. //! diff --git a/rand_pcg/src/lib.rs b/rand_pcg/src/lib.rs index 3ea401debf2..6b9d9d833f0 100644 --- a/rand_pcg/src/lib.rs +++ b/rand_pcg/src/lib.rs @@ -34,24 +34,21 @@ //! Generators implement the [`SeedableRng`] trait. All methods are suitable for //! seeding. Some suggestions: //! -//! 1. Seed **from an integer** via `seed_from_u64`. This uses a hash function -//! internally to yield a (typically) good seed from any input. -//! ``` -//! # use {rand_core::SeedableRng, rand_pcg::Pcg64Mcg}; -//! let rng = Pcg64Mcg::seed_from_u64(1); +//! 1. To automatically seed with a unique seed, use [`SeedableRng::from_rng`] +//! with a master generator (here [`rand::rng()`](https://docs.rs/rand/latest/rand/fn.rng.html)): +//! ```ignore +//! use rand_core::SeedableRng; +//! use rand_pcg::Pcg64Mcg; +//! let rng = Pcg64Mcg::from_rng(&mut rand::rng()); //! # let _: Pcg64Mcg = rng; //! ``` -//! 2. With a fresh seed, **direct from the OS** (implies a syscall): +//! 2. Seed **from an integer** via `seed_from_u64`. This uses a hash function +//! internally to yield a (typically) good seed from any input. //! ``` //! # use {rand_core::SeedableRng, rand_pcg::Pcg64Mcg}; -//! let rng = Pcg64Mcg::from_os_rng(); +//! let rng = Pcg64Mcg::seed_from_u64(1); //! # let _: Pcg64Mcg = rng; //! ``` -//! 3. **From a master generator.** This could be [`rand::rng`] -//! (effectively a fresh seed without the need for a syscall on each usage) -//! or a deterministic generator such as [`rand_chacha::ChaCha8Rng`]. -//! Beware that should a weak master generator be used, correlations may be -//! detectable between the outputs of its child generators. //! //! See also [Seeding RNGs] in the book. //! @@ -77,6 +74,7 @@ //! [Random Values]: https://rust-random.github.io/book/guide-values.html //! [`RngCore`]: rand_core::RngCore //! [`SeedableRng`]: rand_core::SeedableRng +//! [`SeedableRng::from_rng`]: rand_core::SeedableRng#method.from_rng //! [`rand::rng`]: https://docs.rs/rand/latest/rand/fn.rng.html //! [`rand::Rng`]: https://docs.rs/rand/latest/rand/trait.Rng.html //! [`rand_chacha::ChaCha8Rng`]: https://docs.rs/rand_chacha/latest/rand_chacha/struct.ChaCha8Rng.html diff --git a/src/distr/mod.rs b/src/distr/mod.rs index 9ee932417fc..84bf4925a27 100644 --- a/src/distr/mod.rs +++ b/src/distr/mod.rs @@ -182,7 +182,7 @@ use crate::Rng; /// use rand::prelude::*; /// use rand::distr::StandardUniform; /// -/// let val: f32 = StdRng::from_os_rng().sample(StandardUniform); +/// let val: f32 = rand::rng().sample(StandardUniform); /// println!("f32 from [0, 1): {}", val); /// ``` /// diff --git a/src/rngs/small.rs b/src/rngs/small.rs index 1f9e7f5b8c9..67e0d0544f4 100644 --- a/src/rngs/small.rs +++ b/src/rngs/small.rs @@ -40,24 +40,29 @@ type Rng = super::xoshiro256plusplus::Xoshiro256PlusPlus; /// suitable for seeding, but note that, even with a fixed seed, output is not /// [portable]. Some suggestions: /// -/// 1. Seed **from an integer** via `seed_from_u64`. This uses a hash function -/// internally to yield a (typically) good seed from any input. +/// 1. To automatically seed with a unique seed, use [`SeedableRng::from_rng`]: /// ``` -/// # use rand::{SeedableRng, rngs::SmallRng}; -/// let rng = SmallRng::seed_from_u64(1); +/// use rand::SeedableRng; +/// use rand::rngs::SmallRng; +/// let rng = SmallRng::from_rng(&mut rand::rng()); /// # let _: SmallRng = rng; /// ``` -/// 2. With a fresh seed, **direct from the OS** (implies a syscall): +/// or [`SeedableRng::from_os_rng`]: /// ``` -/// # use rand::{SeedableRng, rngs::SmallRng}; +/// # use rand::SeedableRng; +/// # use rand::rngs::SmallRng; /// let rng = SmallRng::from_os_rng(); /// # let _: SmallRng = rng; /// ``` -/// 3. Via [`SmallRng::from_thread_rng`]: +/// 2. To use a deterministic integral seed, use `seed_from_u64`. This uses a +/// hash function internally to yield a (typically) good seed from any +/// input. /// ``` -/// # use rand::rngs::SmallRng; -/// let rng = SmallRng::from_thread_rng(); +/// # use rand::{SeedableRng, rngs::SmallRng}; +/// let rng = SmallRng::seed_from_u64(1); +/// # let _: SmallRng = rng; /// ``` +/// 3. To seed deterministically from text or other input, use [`rand_seeder`]. /// /// See also [Seeding RNGs] in the book. /// @@ -74,6 +79,7 @@ type Rng = super::xoshiro256plusplus::Xoshiro256PlusPlus; /// [rand_pcg]: https://crates.io/crates/rand_pcg /// [rand_xoshiro]: https://crates.io/crates/rand_xoshiro /// [`rand_chacha::ChaCha8Rng`]: https://docs.rs/rand_chacha/latest/rand_chacha/struct.ChaCha8Rng.html +/// [`rand_seeder`]: https://docs.rs/rand_seeder/latest/rand_seeder/ #[derive(Clone, Debug, PartialEq, Eq)] pub struct SmallRng(Rng); @@ -112,19 +118,3 @@ impl RngCore for SmallRng { self.0.fill_bytes(dest) } } - -impl SmallRng { - /// Construct an instance seeded from `rand::rng` - /// - /// # Panics - /// - /// This method panics only if [`crate::rng()`] fails to - /// initialize. - #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] - #[inline(always)] - pub fn from_thread_rng() -> Self { - let mut seed = ::Seed::default(); - crate::rng().fill_bytes(seed.as_mut()); - SmallRng(Rng::from_seed(seed)) - } -} diff --git a/src/rngs/std.rs b/src/rngs/std.rs index d59291b8567..fd2c24e30a8 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -37,15 +37,22 @@ use rand_chacha::ChaCha12Rng as Rng; /// but note that `seed_from_u64` is not suitable for usage where security is /// important. Also note that, even with a fixed seed, output is not [portable]. /// -/// It is suggested to use a fresh seed **direct from the OS** as the most -/// secure and convenient option: +/// Using a fresh seed **direct from the OS** is the most secure option: /// ``` /// # use rand::{SeedableRng, rngs::StdRng}; /// let rng = StdRng::from_os_rng(); /// # let _: StdRng = rng; /// ``` /// -/// See also [Seeding RNGs] in the book. +/// Seeding via [`rand::rng()`](crate::rng()) may be faster: +/// ``` +/// # use rand::{SeedableRng, rngs::StdRng}; +/// let rng = StdRng::from_rng(&mut rand::rng()); +/// # let _: StdRng = rng; +/// ``` +/// +/// Any [`SeedableRng`] method may be used, but note that `seed_from_u64` is not +/// suitable where security is required. See also [Seeding RNGs] in the book. /// /// ## Generation /// From c1f865f10ffecc8a211e98ac7e8c5014d3949f45 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 25 Nov 2024 13:29:51 +0000 Subject: [PATCH 431/443] Bump the MSRV to 1.63.0 (#1536) --- .github/workflows/test.yml | 2 +- CHANGELOG.md | 2 +- Cargo.toml | 2 +- rand_chacha/Cargo.toml | 2 +- rand_core/CHANGELOG.md | 2 +- rand_core/Cargo.toml | 2 +- rand_distr/Cargo.toml | 2 +- rand_pcg/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1108a9cab2a..4a058422434 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,7 +55,7 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu variant: MSRV - toolchain: 1.61.0 + toolchain: 1.63.0 - os: ubuntu-latest deps: sudo apt-get update ; sudo apt install gcc-multilib target: i686-unknown-linux-gnu diff --git a/CHANGELOG.md b/CHANGELOG.md index 56579821c72..8a9fc39ab34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [Unreleased] - Add `rand::distributions::WeightedIndex::{weight, weights, total_weight}` (#1420) - Add `IndexedRandom::choose_multiple_array`, `index::sample_array` (#1453, #1469) -- Bump the MSRV to 1.61.0 +- Bump the MSRV to 1.63.0 - Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) - Move all benchmarks to new `benches` crate (#1439) and migrate to Criterion (#1490) - Annotate panicking methods with `#[track_caller]` (#1442, #1447) diff --git a/Cargo.toml b/Cargo.toml index 0b22e9606b3..73a36ac6c5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["random", "rng"] categories = ["algorithms", "no-std"] autobenches = true edition = "2021" -rust-version = "1.61" +rust-version = "1.63" include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index c15389cdc08..385b9a6eedb 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -13,7 +13,7 @@ ChaCha random number generator keywords = ["random", "rng", "chacha"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.61" +rust-version = "1.63" [package.metadata.docs.rs] all-features = true diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index d422fe30bd4..c8876be32e0 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -- Bump the MSRV to 1.61.0 +- Bump the MSRV to 1.63.0 - The `serde1` feature has been renamed `serde` (#1477) ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 13d2b56a471..5870328c176 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -13,7 +13,7 @@ Core random number generator traits and tools for implementation. keywords = ["random", "rng"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.61" +rust-version = "1.63" [package.metadata.docs.rs] # To build locally: diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index efcfed67ba8..211d30c0f25 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -13,7 +13,7 @@ Sampling from random number distributions keywords = ["random", "rng", "distribution", "probability"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.61" +rust-version = "1.63" include = ["/src", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 1bd7398ca6a..a66abb05214 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -13,7 +13,7 @@ Selected PCG random number generators keywords = ["random", "rng", "pcg"] categories = ["algorithms", "no-std"] edition = "2021" -rust-version = "1.61" +rust-version = "1.63" [package.metadata.docs.rs] all-features = true From 0ff946c526f55092fa70dc9c8ecda3954cadc3fe Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 26 Nov 2024 09:48:51 +0000 Subject: [PATCH 432/443] Make getrandom a non-pub dep; rename feature to os_rng (#1537) --- .github/workflows/gh-pages.yml | 2 +- .github/workflows/test.yml | 6 ++-- CHANGELOG.md | 1 + Cargo.toml | 6 ++-- README.md | 15 +++++----- rand_chacha/CHANGELOG.md | 1 + rand_chacha/Cargo.toml | 4 +-- rand_chacha/README.md | 2 +- rand_core/CHANGELOG.md | 1 + rand_core/Cargo.toml | 2 +- rand_core/src/lib.rs | 12 ++++---- rand_core/src/os.rs | 53 ++++++++++++++++++++++++++++++---- rand_pcg/CHANGELOG.md | 1 + rand_pcg/Cargo.toml | 4 +-- src/lib.rs | 22 +++++++------- src/prelude.rs | 2 +- src/rngs/mod.rs | 6 ++-- src/rngs/std.rs | 2 +- src/rngs/thread.rs | 2 +- 19 files changed, 95 insertions(+), 49 deletions(-) diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index c0cab03b926..1d83a77bd7f 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -30,7 +30,7 @@ jobs: RUSTDOCFLAGS: --cfg doc_cfg # --all builds all crates, but with default features for other crates (okay in this case) run: | - cargo doc --all --features nightly,serde,getrandom,small_rng + cargo doc --all --all-features --no-deps cp utils/redirect.html target/doc/index.html rm target/doc/.lock diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a058422434..4a8989c513e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -90,8 +90,8 @@ jobs: - name: Test rand run: | cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng,unbiased - cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng + cargo build --target ${{ matrix.target }} --no-default-features --features alloc,os_rng,small_rng,unbiased + cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,os_rng,small_rng cargo test --target ${{ matrix.target }} --examples - name: Test rand (all stable features) run: | @@ -100,7 +100,7 @@ jobs: run: | cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features - cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features --features=alloc,getrandom + cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features --features=alloc,os_rng - name: Test rand_distr run: | cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a9fc39ab34..3e53dc3ba25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Remove impl of `Distribution>` for `Standard` (#1526) - Remove `SmallRng::from_thread_rng` (#1532) - Remove first parameter (`rng`) of `ReseedingRng::new` (#1533) +- Rename feature `getrandom` to `os_rng` ## [0.9.0-alpha.1] - 2024-03-18 - Add the `Slice::num_choices` method to the Slice distribution (#1402) diff --git a/Cargo.toml b/Cargo.toml index 73a36ac6c5e..b5b254ba2a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ features = ["small_rng", "serde"] [features] # Meta-features: -default = ["std", "std_rng", "getrandom", "small_rng"] +default = ["std", "std_rng", "os_rng", "small_rng"] nightly = [] # some additions requiring nightly Rust serde = ["dep:serde", "rand_core/serde"] @@ -39,8 +39,8 @@ std = ["rand_core/std", "rand_chacha?/std", "alloc"] # Option: "alloc" enables support for Vec and Box when not using "std" alloc = ["rand_core/alloc"] -# Option: use getrandom package for seeding -getrandom = ["rand_core/getrandom"] +# Option: enable OsRng +os_rng = ["rand_core/os_rng"] # Option (requires nightly Rust): experimental SIMD support simd_support = ["zerocopy/simd-nightly"] diff --git a/README.md b/README.md index b8e089099f1..0ac3aaac18c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Rand is a set of crates supporting (pseudo-)random generators: - With fast implementations of both [strong](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs) and [small](https://rust-random.github.io/book/guide-rngs.html#basic-pseudo-random-number-generators-prngs) generators: [`rand::rngs`](https://docs.rs/rand/latest/rand/rngs/index.html), and more RNGs: [`rand_chacha`](https://docs.rs/rand_chacha), [`rand_xoshiro`](https://docs.rs/rand_xoshiro/), [`rand_pcg`](https://docs.rs/rand_pcg/), [rngs repo](https://github.com/rust-random/rngs/) - [`rand::rng`](https://docs.rs/rand/latest/rand/fn.rng.html) is an asymptotically-fast, automatically-seeded and reasonably strong generator available on all `std` targets -- Direct support for seeding generators from the [`getrandom` crate](https://crates.io/crates/getrandom) +- Direct support for seeding generators from the [getrandom] crate With broad support for random value generation and random processes: @@ -80,8 +80,7 @@ Rand is built with these features enabled by default: - `std` enables functionality dependent on the `std` lib - `alloc` (implied by `std`) enables functionality requiring an allocator -- `getrandom` (implied by `std`) is an optional dependency providing the code - behind `rngs::OsRng` +- `os_rng` (implied by `std`) enables `rngs::OsRng`, using the [getrandom] crate - `std_rng` enables inclusion of `StdRng`, `ThreadRng` Optionally, the following dependencies can be enabled: @@ -101,23 +100,23 @@ experimental `simd_support` feature. Rand supports limited functionality in `no_std` mode (enabled via `default-features = false`). In this case, `OsRng` and `from_os_rng` are -unavailable (unless `getrandom` is enabled), large parts of `seq` are +unavailable (unless `os_rng` is enabled), large parts of `seq` are unavailable (unless `alloc` is enabled), and `ThreadRng` is unavailable. ## Portability and platform support Many (but not all) algorithms are intended to have reproducible output. Read more in the book: [Portability](https://rust-random.github.io/book/portability.html). -The Rand library supports a variety of CPU architectures. Platform integration is outsourced to [getrandom](https://docs.rs/getrandom/latest/getrandom/). +The Rand library supports a variety of CPU architectures. Platform integration is outsourced to [getrandom]. ### WASM support Seeding entropy from OS on WASM target `wasm32-unknown-unknown` is not *automatically* supported by `rand` or `getrandom`. If you are fine with -seeding the generator manually, you can disable the `getrandom` feature +seeding the generator manually, you can disable the `os_rng` feature and use the methods on the `SeedableRng` trait. To enable seeding from OS, either use a different target such as `wasm32-wasi` or add a direct -dependency on `getrandom` with the `js` feature (if the target supports +dependency on [getrandom] with the `js` feature (if the target supports JavaScript). See [getrandom#WebAssembly support](https://docs.rs/getrandom/latest/getrandom/#webassembly-support). @@ -128,3 +127,5 @@ Apache License (Version 2.0). See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT), and [COPYRIGHT](COPYRIGHT) for details. + +[getrandom]: https://crates.io/crates/getrandom diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index 8543c1e4033..68dba7dfdc2 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - The `serde1` feature has been renamed `serde` (#1477) +- Rename feature `getrandom` to `os_rng` ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 385b9a6eedb..168b39def57 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -27,10 +27,10 @@ serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] # Only to test serde serde_json = "1.0" -rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["getrandom"] } +rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["os_rng"] } [features] default = ["std"] -getrandom = ["rand_core/getrandom"] +os_rng = ["rand_core/os_rng"] std = ["ppv-lite86/std", "rand_core/std"] serde = ["dep:serde"] diff --git a/rand_chacha/README.md b/rand_chacha/README.md index 1b555ad086b..35a49a366d0 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -36,7 +36,7 @@ Links: `rand_chacha` is `no_std` compatible when disabling default features; the `std` feature can be explicitly required to re-enable `std` support. Using `std` allows detection of CPU features and thus better optimisation. Using `std` -also enables `getrandom` functionality, such as `ChaCha20Rng::from_os_rng()`. +also enables `os_rng` functionality, such as `ChaCha20Rng::from_os_rng()`. # License diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index c8876be32e0..a2d60c2da11 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Bump the MSRV to 1.63.0 - The `serde1` feature has been renamed `serde` (#1477) +- Rename feature `getrandom` to `os_rng` ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 5870328c176..6f11b895a30 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -27,7 +27,7 @@ all-features = true [features] std = ["alloc", "getrandom?/std"] alloc = [] # enables Vec and Box support without std -getrandom = ["dep:getrandom"] +os_rng = ["dep:getrandom"] serde = ["dep:serde"] # enables serde for BlockRng wrapper [dependencies] diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index 4e75faf25b5..adc71770812 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -45,13 +45,11 @@ use core::{fmt, ops::DerefMut}; pub mod block; pub mod impls; pub mod le; -#[cfg(feature = "getrandom")] +#[cfg(feature = "os_rng")] mod os; -#[cfg(feature = "getrandom")] -pub use getrandom; -#[cfg(feature = "getrandom")] -pub use os::OsRng; +#[cfg(feature = "os_rng")] +pub use os::{OsError, OsRng}; /// Implementation-level interface for RNGs /// @@ -495,7 +493,7 @@ pub trait SeedableRng: Sized { /// /// [`getrandom`]: https://docs.rs/getrandom /// [`try_from_os_rng`]: SeedableRng::try_from_os_rng - #[cfg(feature = "getrandom")] + #[cfg(feature = "os_rng")] fn from_os_rng() -> Self { match Self::try_from_os_rng() { Ok(res) => res, @@ -511,7 +509,7 @@ pub trait SeedableRng: Sized { /// `from_rng(&mut rand::rng()).unwrap()`. /// /// [`getrandom`]: https://docs.rs/getrandom - #[cfg(feature = "getrandom")] + #[cfg(feature = "os_rng")] fn try_from_os_rng() -> Result { let mut seed = Self::Seed::default(); getrandom::getrandom(seed.as_mut())?; diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index 78b689bc021..44efe700c13 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -19,7 +19,7 @@ use getrandom::getrandom; /// [getrandom] documentation for details. /// /// This struct is available as `rand_core::OsRng` and as `rand::rngs::OsRng`. -/// In both cases, this requires the crate feature `getrandom` or `std` +/// In both cases, this requires the crate feature `os_rng` or `std` /// (enabled by default in `rand` but not in `rand_core`). /// /// # Blocking and error handling @@ -47,26 +47,69 @@ use getrandom::getrandom; #[derive(Clone, Copy, Debug, Default)] pub struct OsRng; +/// Error type of [`OsRng`] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct OsError(getrandom::Error); + +impl core::fmt::Display for OsError { + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(f) + } +} + +// NOTE: this can use core::error::Error from rustc 1.81.0 +#[cfg(feature = "std")] +impl std::error::Error for OsError { + #[inline] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + std::error::Error::source(&self.0) + } +} + +impl OsError { + /// Extract the raw OS error code (if this error came from the OS) + /// + /// This method is identical to [`std::io::Error::raw_os_error()`][1], except + /// that it works in `no_std` contexts. If this method returns `None`, the + /// error value can still be formatted via the `Display` implementation. + /// + /// [1]: https://doc.rust-lang.org/std/io/struct.Error.html#method.raw_os_error + #[inline] + pub fn raw_os_error(self) -> Option { + self.0.raw_os_error() + } + + /// Extract the bare error code. + /// + /// This code can either come from the underlying OS, or be a custom error. + /// Use [`OsError::raw_os_error()`] to disambiguate. + #[inline] + pub const fn code(self) -> core::num::NonZeroU32 { + self.0.code() + } +} + impl TryRngCore for OsRng { - type Error = getrandom::Error; + type Error = OsError; #[inline] fn try_next_u32(&mut self) -> Result { let mut buf = [0u8; 4]; - getrandom(&mut buf)?; + getrandom(&mut buf).map_err(OsError)?; Ok(u32::from_ne_bytes(buf)) } #[inline] fn try_next_u64(&mut self) -> Result { let mut buf = [0u8; 8]; - getrandom(&mut buf)?; + getrandom(&mut buf).map_err(OsError)?; Ok(u64::from_ne_bytes(buf)) } #[inline] fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { - getrandom(dest)?; + getrandom(dest).map_err(OsError)?; Ok(()) } } diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index 80940a93a07..0133909a97b 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - The `serde1` feature has been renamed `serde` (#1477) +- Rename feature `getrandom` to `os_rng` ## [0.9.0-alpha.1] - 2024-03-18 diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index a66abb05214..fb4dd17b007 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -21,7 +21,7 @@ rustdoc-args = ["--generate-link-to-definition"] [features] serde = ["dep:serde"] -getrandom = ["rand_core/getrandom"] +os_rng = ["rand_core/os_rng"] [dependencies] rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1" } @@ -32,4 +32,4 @@ serde = { version = "1", features = ["derive"], optional = true } # deps yet, see: https://github.com/rust-lang/cargo/issues/1596 # Versions prior to 1.1.4 had incorrect minimal dependencies. bincode = { version = "1.1.4" } -rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["getrandom"] } +rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["os_rng"] } diff --git a/src/lib.rs b/src/lib.rs index 5b410ca8cb4..192605a5021 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,13 +103,13 @@ pub mod rngs; pub mod seq; // Public exports -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] pub use crate::rngs::thread::rng; /// Access the thread-local generator /// /// Use [`rand::rng()`](rng()) instead. -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] #[deprecated(since = "0.9.0", note = "renamed to `rng`")] #[inline] pub fn thread_rng() -> crate::rngs::ThreadRng { @@ -118,7 +118,7 @@ pub fn thread_rng() -> crate::rngs::ThreadRng { pub use rng::{Fill, Rng}; -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] use crate::distr::{Distribution, StandardUniform}; /// Generate a random value using the thread-local random number generator. @@ -159,7 +159,7 @@ use crate::distr::{Distribution, StandardUniform}; /// /// [`StandardUniform`]: distr::StandardUniform /// [`ThreadRng`]: rngs::ThreadRng -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] #[inline] pub fn random() -> T where @@ -179,7 +179,7 @@ where /// let v: Vec = rand::random_iter().take(5).collect(); /// println!("{v:?}"); /// ``` -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] #[inline] pub fn random_iter() -> distr::DistIter where @@ -204,7 +204,7 @@ where /// ``` /// Note that the first example can also be achieved (without `collect`'ing /// to a `Vec`) using [`seq::IteratorRandom::choose`]. -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] #[inline] pub fn random_range(range: R) -> T where @@ -228,7 +228,7 @@ where /// # Panics /// /// If `p < 0` or `p > 1`. -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] #[inline] #[track_caller] pub fn random_bool(p: f64) -> bool { @@ -260,7 +260,7 @@ pub fn random_bool(p: f64) -> bool { /// ``` /// /// [`Bernoulli`]: distr::Bernoulli -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] #[inline] #[track_caller] pub fn random_ratio(numerator: u32, denominator: u32) -> bool { @@ -282,7 +282,7 @@ pub fn random_ratio(numerator: u32, denominator: u32) -> bool { /// Note that you can instead use [`random()`] to generate an array of random /// data, though this is slower for small elements (smaller than the RNG word /// size). -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] #[inline] #[track_caller] pub fn fill(dest: &mut T) { @@ -302,7 +302,7 @@ mod test { } #[test] - #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] + #[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] fn test_random() { let _n: u64 = random(); let _f: f32 = random(); @@ -316,7 +316,7 @@ mod test { } #[test] - #[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] + #[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] fn test_range() { let _n: usize = random_range(42..=43); let _f: f32 = random_range(42.0..43.0); diff --git a/src/prelude.rs b/src/prelude.rs index d41ad6b494b..0e15855c928 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -27,7 +27,7 @@ pub use crate::rngs::SmallRng; #[doc(no_inline)] pub use crate::rngs::StdRng; #[doc(no_inline)] -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] pub use crate::rngs::ThreadRng; #[doc(no_inline)] pub use crate::seq::{IndexedMutRandom, IndexedRandom, IteratorRandom, SliceRandom}; diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index bfb5bf6e960..32e12d68892 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -95,15 +95,15 @@ mod xoshiro256plusplus; #[cfg(feature = "std_rng")] mod std; -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] pub(crate) mod thread; #[cfg(feature = "small_rng")] pub use self::small::SmallRng; #[cfg(feature = "std_rng")] pub use self::std::StdRng; -#[cfg(all(feature = "std", feature = "std_rng", feature = "getrandom"))] +#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] pub use self::thread::ThreadRng; -#[cfg(feature = "getrandom")] +#[cfg(feature = "os_rng")] pub use rand_core::OsRng; diff --git a/src/rngs/std.rs b/src/rngs/std.rs index fd2c24e30a8..6e1658e7453 100644 --- a/src/rngs/std.rs +++ b/src/rngs/std.rs @@ -10,7 +10,7 @@ use rand_core::{CryptoRng, RngCore, SeedableRng}; -#[cfg(any(test, feature = "getrandom"))] +#[cfg(any(test, feature = "os_rng"))] pub(crate) use rand_chacha::ChaCha12Core as Core; use rand_chacha::ChaCha12Rng as Rng; diff --git a/src/rngs/thread.rs b/src/rngs/thread.rs index 38446759ab9..7e5203214a4 100644 --- a/src/rngs/thread.rs +++ b/src/rngs/thread.rs @@ -100,7 +100,7 @@ impl ThreadRng { /// Immediately reseed the generator /// /// This discards any remaining random data in the cache. - pub fn reseed(&mut self) -> Result<(), rand_core::getrandom::Error> { + pub fn reseed(&mut self) -> Result<(), rand_core::OsError> { // SAFETY: We must make sure to stop using `rng` before anyone else // creates another mutable reference let rng = unsafe { &mut *self.rng.get() }; From bf9d429e89ee1baa633e7eec5b75f89dcd78e7f6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 26 Nov 2024 11:22:55 +0000 Subject: [PATCH 433/443] Add KS tests for weighted sampling; use A-ExpJ alg with log-keys (#1530) - Extra testing for weighted sampling - Fix IndexedRandom::choose_multiple_weighted with very small keys - Use A-ExpJ algorithm with BinaryHeap for better performance with large length / amount --- distr_test/Cargo.toml | 2 +- distr_test/tests/weighted.rs | 235 +++++++++++++++++++++++++++++++++++ src/seq/index.rs | 54 ++++---- src/seq/slice.rs | 16 +-- 4 files changed, 277 insertions(+), 30 deletions(-) create mode 100644 distr_test/tests/weighted.rs diff --git a/distr_test/Cargo.toml b/distr_test/Cargo.toml index 7f37853b3c1..36314b37cf0 100644 --- a/distr_test/Cargo.toml +++ b/distr_test/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" publish = false [dev-dependencies] -rand_distr = { path = "../rand_distr", version = "=0.5.0-alpha.1", default-features = false } +rand_distr = { path = "../rand_distr", version = "=0.5.0-alpha.1", default-features = false, features = ["alloc"] } rand = { path = "..", version = "=0.9.0-alpha.1", features = ["small_rng"] } num-traits = "0.2.19" # Special functions for testing distributions diff --git a/distr_test/tests/weighted.rs b/distr_test/tests/weighted.rs new file mode 100644 index 00000000000..cf87b3ee634 --- /dev/null +++ b/distr_test/tests/weighted.rs @@ -0,0 +1,235 @@ +// Copyright 2024 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod ks; +use ks::test_discrete; +use rand::distr::{Distribution, WeightedIndex}; +use rand::seq::{IndexedRandom, IteratorRandom}; +use rand_distr::{WeightedAliasIndex, WeightedTreeIndex}; + +/// Takes the unnormalized pdf and creates the cdf of a discrete distribution +fn make_cdf(num: usize, f: impl Fn(i64) -> f64) -> impl Fn(i64) -> f64 { + let mut cdf = Vec::with_capacity(num); + let mut ac = 0.0; + for i in 0..num { + ac += f(i as i64); + cdf.push(ac); + } + + let frac = 1.0 / ac; + for x in &mut cdf { + *x *= frac; + } + + move |i| { + if i < 0 { + 0.0 + } else { + cdf[i as usize] + } + } +} + +#[test] +fn weighted_index() { + fn test_weights(num: usize, weight: impl Fn(i64) -> f64) { + let distr = WeightedIndex::new((0..num).map(|i| weight(i as i64))).unwrap(); + test_discrete(0, distr, make_cdf(num, weight)); + } + + test_weights(100, |_| 1.0); + test_weights(100, |i| ((i + 1) as f64).ln()); + test_weights(100, |i| i as f64); + test_weights(100, |i| (i as f64).powi(3)); + test_weights(100, |i| 1.0 / ((i + 1) as f64)); +} + +#[test] +fn weighted_alias_index() { + fn test_weights(num: usize, weight: impl Fn(i64) -> f64) { + let weights = (0..num).map(|i| weight(i as i64)).collect(); + let distr = WeightedAliasIndex::new(weights).unwrap(); + test_discrete(0, distr, make_cdf(num, weight)); + } + + test_weights(100, |_| 1.0); + test_weights(100, |i| ((i + 1) as f64).ln()); + test_weights(100, |i| i as f64); + test_weights(100, |i| (i as f64).powi(3)); + test_weights(100, |i| 1.0 / ((i + 1) as f64)); +} + +#[test] +fn weighted_tree_index() { + fn test_weights(num: usize, weight: impl Fn(i64) -> f64) { + let distr = WeightedTreeIndex::new((0..num).map(|i| weight(i as i64))).unwrap(); + test_discrete(0, distr, make_cdf(num, weight)); + } + + test_weights(100, |_| 1.0); + test_weights(100, |i| ((i + 1) as f64).ln()); + test_weights(100, |i| i as f64); + test_weights(100, |i| (i as f64).powi(3)); + test_weights(100, |i| 1.0 / ((i + 1) as f64)); +} + +#[test] +fn choose_weighted_indexed() { + struct Adapter f64>(Vec, F); + impl f64> Distribution for Adapter { + fn sample(&self, rng: &mut R) -> i64 { + *IndexedRandom::choose_weighted(&self.0[..], rng, |i| (self.1)(*i)).unwrap() + } + } + + fn test_weights(num: usize, weight: impl Fn(i64) -> f64) { + let distr = Adapter((0..num).map(|i| i as i64).collect(), &weight); + test_discrete(0, distr, make_cdf(num, &weight)); + } + + test_weights(100, |_| 1.0); + test_weights(100, |i| ((i + 1) as f64).ln()); + test_weights(100, |i| i as f64); + test_weights(100, |i| (i as f64).powi(3)); + test_weights(100, |i| 1.0 / ((i + 1) as f64)); +} + +#[test] +fn choose_one_weighted_indexed() { + struct Adapter f64>(Vec, F); + impl f64> Distribution for Adapter { + fn sample(&self, rng: &mut R) -> i64 { + *IndexedRandom::choose_multiple_weighted(&self.0[..], rng, 1, |i| (self.1)(*i)) + .unwrap() + .next() + .unwrap() + } + } + + fn test_weights(num: usize, weight: impl Fn(i64) -> f64) { + let distr = Adapter((0..num).map(|i| i as i64).collect(), &weight); + test_discrete(0, distr, make_cdf(num, &weight)); + } + + test_weights(100, |_| 1.0); + test_weights(100, |i| ((i + 1) as f64).ln()); + test_weights(100, |i| i as f64); + test_weights(100, |i| (i as f64).powi(3)); + test_weights(100, |i| 1.0 / ((i + 1) as f64)); +} + +#[test] +fn choose_two_weighted_indexed() { + struct Adapter f64>(Vec, F); + impl f64> Distribution for Adapter { + fn sample(&self, rng: &mut R) -> i64 { + let mut iter = + IndexedRandom::choose_multiple_weighted(&self.0[..], rng, 2, |i| (self.1)(*i)) + .unwrap(); + let mut a = *iter.next().unwrap(); + let mut b = *iter.next().unwrap(); + assert!(iter.next().is_none()); + if b < a { + std::mem::swap(&mut a, &mut b); + } + a * self.0.len() as i64 + b + } + } + + fn test_weights(num: usize, weight: impl Fn(i64) -> f64) { + let distr = Adapter((0..num).map(|i| i as i64).collect(), &weight); + + let pmf1 = (0..num).map(|i| weight(i as i64)).collect::>(); + let sum: f64 = pmf1.iter().sum(); + let frac = 1.0 / sum; + + let mut ac = 0.0; + let mut cdf = Vec::with_capacity(num * num); + for a in 0..num { + for b in 0..num { + if a < b { + let pa = pmf1[a] * frac; + let pab = pa * pmf1[b] / (sum - pmf1[a]); + + let pb = pmf1[b] * frac; + let pba = pb * pmf1[a] / (sum - pmf1[b]); + + ac += pab + pba; + } + cdf.push(ac); + } + } + assert!((cdf.last().unwrap() - 1.0).abs() < 1e-9); + + let cdf = |i| { + if i < 0 { + 0.0 + } else { + cdf[i as usize] + } + }; + + test_discrete(0, distr, cdf); + } + + test_weights(100, |_| 1.0); + test_weights(100, |i| ((i + 1) as f64).ln()); + test_weights(100, |i| i as f64); + test_weights(100, |i| (i as f64).powi(3)); + test_weights(100, |i| 1.0 / ((i + 1) as f64)); + test_weights(10, |i| ((i + 1) as f64).powi(-8)); +} + +#[test] +fn choose_iterator() { + struct Adapter(I); + impl> Distribution for Adapter { + fn sample(&self, rng: &mut R) -> i64 { + IteratorRandom::choose(self.0.clone(), rng).unwrap() + } + } + + let distr = Adapter((0..100).map(|i| i as i64)); + test_discrete(0, distr, make_cdf(100, |_| 1.0)); +} + +#[test] +fn choose_stable_iterator() { + struct Adapter(I); + impl> Distribution for Adapter { + fn sample(&self, rng: &mut R) -> i64 { + IteratorRandom::choose_stable(self.0.clone(), rng).unwrap() + } + } + + let distr = Adapter((0..100).map(|i| i as i64)); + test_discrete(0, distr, make_cdf(100, |_| 1.0)); +} + +#[test] +fn choose_two_iterator() { + struct Adapter(I); + impl> Distribution for Adapter { + fn sample(&self, rng: &mut R) -> i64 { + let mut buf = [0; 2]; + IteratorRandom::choose_multiple_fill(self.0.clone(), rng, &mut buf); + buf.sort_unstable(); + assert!(buf[0] < 99 && buf[1] >= 1); + let a = buf[0]; + 4950 - (99 - a) * (100 - a) / 2 + buf[1] - a - 1 + } + } + + let distr = Adapter((0..100).map(|i| i as i64)); + + test_discrete( + 0, + distr, + |i| if i < 0 { 0.0 } else { (i + 1) as f64 / 4950.0 }, + ); +} diff --git a/src/seq/index.rs b/src/seq/index.rs index 70231dde2ca..852bdac76c4 100644 --- a/src/seq/index.rs +++ b/src/seq/index.rs @@ -333,8 +333,8 @@ where /// ordering). The weights are to be provided by the input function `weights`, /// which will be called once for each index. /// -/// This implementation uses the algorithm described by Efraimidis and Spirakis -/// in this paper: +/// This implementation is based on the algorithm A-ExpJ as found in +/// [Efraimidis and Spirakis, 2005](https://doi.org/10.1016/j.ipl.2005.11.003). /// It uses `O(length + amount)` space and `O(length)` time. /// /// Error cases: @@ -354,7 +354,7 @@ where N: UInt, IndexVec: From>, { - use std::cmp::Ordering; + use std::{cmp::Ordering, collections::BinaryHeap}; if amount == N::zero() { return Ok(IndexVec::U32(Vec::new())); @@ -373,9 +373,9 @@ where impl Ord for Element { fn cmp(&self, other: &Self) -> Ordering { - // partial_cmp will always produce a value, - // because we check that the weights are not nan - self.key.partial_cmp(&other.key).unwrap() + // unwrap() should not panic since weights should not be NaN + // We reverse so that BinaryHeap::peek shows the smallest item + self.key.partial_cmp(&other.key).unwrap().reverse() } } @@ -387,12 +387,14 @@ where impl Eq for Element {} - let mut candidates = Vec::with_capacity(length.as_usize()); + let mut candidates = BinaryHeap::with_capacity(amount.as_usize()); let mut index = N::zero(); - while index < length { + while index < length && candidates.len() < amount.as_usize() { let weight = weight(index.as_usize()).into(); if weight > 0.0 { - let key = rng.random::().powf(1.0 / weight); + // We use the log of the key used in A-ExpJ to improve precision + // for small weights: + let key = rng.random::().ln() / weight; candidates.push(Element { index, key }); } else if !(weight >= 0.0) { return Err(WeightError::InvalidWeight); @@ -401,23 +403,33 @@ where index += N::one(); } - let avail = candidates.len(); - if avail < amount.as_usize() { + if candidates.len() < amount.as_usize() { return Err(WeightError::InsufficientNonZero); } - // Partially sort the array to find the `amount` elements with the greatest - // keys. Do this by using `select_nth_unstable` to put the elements with - // the *smallest* keys at the beginning of the list in `O(n)` time, which - // provides equivalent information about the elements with the *greatest* keys. - let (_, mid, greater) = candidates.select_nth_unstable(avail - amount.as_usize()); + let mut x = rng.random::().ln() / candidates.peek().unwrap().key; + while index < length { + let weight = weight(index.as_usize()).into(); + if weight > 0.0 { + x -= weight; + if x <= 0.0 { + let min_candidate = candidates.pop().unwrap(); + let t = (min_candidate.key * weight).exp(); + let key = rng.random_range(t..1.0).ln() / weight; + candidates.push(Element { index, key }); + + x = rng.random::().ln() / candidates.peek().unwrap().key; + } + } else if !(weight >= 0.0) { + return Err(WeightError::InvalidWeight); + } - let mut result: Vec = Vec::with_capacity(amount.as_usize()); - result.push(mid.index); - for element in greater { - result.push(element.index); + index += N::one(); } - Ok(IndexVec::from(result)) + + Ok(IndexVec::from( + candidates.iter().map(|elt| elt.index).collect(), + )) } /// Randomly sample exactly `amount` indices from `0..length`, using Floyd's diff --git a/src/seq/slice.rs b/src/seq/slice.rs index 4144f913451..1fc10c09857 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -732,17 +732,17 @@ mod test { use super::*; // The theoretical probabilities of the different outcomes are: - // AB: 0.5 * 0.5 = 0.250 - // AC: 0.5 * 0.5 = 0.250 - // BA: 0.25 * 0.67 = 0.167 - // BC: 0.25 * 0.33 = 0.082 - // CA: 0.25 * 0.67 = 0.167 - // CB: 0.25 * 0.33 = 0.082 - let choices = [('a', 2), ('b', 1), ('c', 1)]; + // AB: 0.5 * 0.667 = 0.3333 + // AC: 0.5 * 0.333 = 0.1667 + // BA: 0.333 * 0.75 = 0.25 + // BC: 0.333 * 0.25 = 0.0833 + // CA: 0.167 * 0.6 = 0.1 + // CB: 0.167 * 0.4 = 0.0667 + let choices = [('a', 3), ('b', 2), ('c', 1)]; let mut rng = crate::test::rng(414); let mut results = [0i32; 3]; - let expected_results = [4167, 4167, 1666]; + let expected_results = [5833, 2667, 1500]; for _ in 0..10000 { let result = choices .choose_multiple_weighted(&mut rng, 2, |item| item.1) From 3fac49fe890da599d671f2ad02074f4961480878 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 26 Nov 2024 13:29:11 +0000 Subject: [PATCH 434/443] Prepare 0.9.0-beta.0 (#1535) - Update version numbers - Update CHANGELOGs - Add an extra benchmark --- CHANGELOG.md | 117 ++++++++++++++++++---------------- Cargo.lock.msrv | 10 +-- Cargo.toml | 8 +-- README.md | 7 +- benches/benches/seq_choose.rs | 27 ++++++++ distr_test/Cargo.toml | 4 +- rand_chacha/CHANGELOG.md | 13 ++-- rand_chacha/Cargo.toml | 6 +- rand_core/CHANGELOG.md | 24 +++---- rand_core/Cargo.toml | 2 +- rand_distr/CHANGELOG.md | 51 ++++++++------- rand_distr/Cargo.toml | 8 +-- rand_pcg/CHANGELOG.md | 13 ++-- rand_pcg/Cargo.toml | 6 +- src/distr/integer.rs | 11 ++++ 15 files changed, 177 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e53dc3ba25..974ff205511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,79 +8,86 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. -## [Unreleased] -- Add `rand::distributions::WeightedIndex::{weight, weights, total_weight}` (#1420) -- Add `IndexedRandom::choose_multiple_array`, `index::sample_array` (#1453, #1469) -- Bump the MSRV to 1.63.0 -- Rename `Rng::gen` to `Rng::random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1435) -- Move all benchmarks to new `benches` crate (#1439) and migrate to Criterion (#1490) -- Annotate panicking methods with `#[track_caller]` (#1442, #1447) -- Enable feature `small_rng` by default (#1455) -- Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) -- Fix portability of `rand::distributions::Slice` (#1469) -- Rename `rand::distributions` to `rand::distr` (#1470) -- The `serde1` feature has been renamed `serde` (#1477) -- The implicit feature `rand_chacha` has been removed. This is enabled by `std_rng`. (#1473) -- Mark `WeightError` as `#[non_exhaustive]` (#1480). -- Add `p()` for `Bernoulli` to access probability (#1481) -- Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) -- Require `Clone` and `AsRef` bound for `SeedableRng::Seed`. (#1491) -- Improve SmallRng initialization performance (#1482) -- Rename `Rng::gen_iter` to `random_iter` (#1500) -- Rename `rand::thread_rng()` to `rand::rng()`, and remove from the prelude (#1506) -- Remove `rand::random()` from the prelude (#1506) -- Rename `Rng::gen_range` to `random_range`, `gen_bool` to `random_bool`, `gen_ratio` to `random_ratio` (#1505) -- Rename `Standard` to `StandardUniform` (#1526) -- Remove impl of `Distribution>` for `Standard` (#1526) -- Remove `SmallRng::from_thread_rng` (#1532) -- Remove first parameter (`rng`) of `ReseedingRng::new` (#1533) -- Rename feature `getrandom` to `os_rng` +## [0.9.0-beta.0] - 2024-11-25 +This is a pre-release. To depend on this version, use `rand = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). -## [0.9.0-alpha.1] - 2024-03-18 -- Add the `Slice::num_choices` method to the Slice distribution (#1402) +### Security and unsafe +- Policy: "rand is not a crypto library" (#1514) +- Remove fork-protection from `ReseedingRng` and `ThreadRng`. Instead, it is recommended to call `ThreadRng::reseed` on fork. (#1379) +- Use `zerocopy` to replace some `unsafe` code (#1349, #1393, #1446, #1502) -### Generators -- `ReseedingRng::reseed` also resets the random data cache. -- Remove fork-protection from `ReseedingRng` and `ThreadRng`. Instead, it is recommended to call `ThreadRng::reseed` on fork. - -## [0.9.0-alpha.0] - 2024-02-18 -This is a pre-release. To depend on this version, use `rand = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). +### Compilation options +- Bump the MSRV to 1.63.0 (#1207, #1246, #1269, #1341, #1416, #1536); note that 1.60.0 may work for dependents when using `--ignore-rust-version` +- Support `std` feature without `getrandom` or `rand_chacha` (#1354) +- Improve `thread_rng` related docs (#1257) +- The `serde1` feature has been renamed `serde` (#1477) +- The implicit feature `rand_chacha` has been removed. This is enabled by `std_rng`. (#1473) +- Enable feature `small_rng` by default (#1455) +- Rename feature `getrandom` to `os_rng` (#1537) + +### Inherited changes from `rand_core` +- Add fn `RngCore::read_adapter` implementing `std::io::Read` (#1267) +- Add trait `CryptoBlockRng: BlockRngCore`; make `trait CryptoRng: RngCore` (#1273) +- Add traits `TryRngCore`, `TryCryptoRng` (#1424, #1499) +- Add bounds `Clone` and `AsRef` to associated type `SeedableRng::Seed` (#1491) + +### Rng trait and top-level fns +- Rename fn `rand::thread_rng()` to `rand::rng()`, and remove from the prelude (#1506) +- Add top-level fns `random_iter`, `random_range`, `random_bool`, `random_ratio`, `fill` (#1488) +- Remove fn `rand::random()` from the prelude (#1506) +- Re-introduce fn `Rng::gen_iter` as `random_iter` (#1305, #1500) +- Rename fn `Rng::gen` to `random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1438) +- Rename fns `Rng::gen_range` to `random_range`, `gen_bool` to `random_bool`, `gen_ratio` to `random_ratio` (#1505) +- Annotate panicking methods with `#[track_caller]` (#1442, #1447) -### Generators -- Change `SmallRng::seed_from_u64` implementation (#1203) -- Replace `SeedableRng` impl for `SmallRng` with inherent methods, excluding `fn from_seed` (#1368) +### RNGs +- Make `ReseedingRng::reseed` discard remaining data from the last block generated (#1379) +- Change fn `SmallRng::seed_from_u64` implementation (#1203) +- Fix `::Seed` size to 256 bits (#1455) +- Remove first parameter (`rng`) of `ReseedingRng::new` (#1533) +- Improve SmallRng initialization performance (#1482) ### Sequences -- Simpler and faster implementation of Floyd's F2 (#1277). This - changes some outputs from `rand::seq::index::sample` and - `rand::seq::SliceRandom::choose_multiple`. +- Optimize fn `sample_floyd`, affecting output of `rand::seq::index::sample` and `rand::seq::SliceRandom::choose_multiple` (#1277) - New, faster algorithms for `IteratorRandom::choose` and `choose_stable` (#1268) - New, faster algorithms for `SliceRandom::shuffle` and `partial_shuffle` (#1272) -- Re-introduce `Rng::gen_iter` (#1305) - Split trait `SliceRandom` into `IndexedRandom`, `IndexedMutRandom`, `SliceRandom` (#1382) +- Add `IndexedRandom::choose_multiple_array`, `index::sample_array` (#1453, #1469) +- Fix `IndexdRandom::choose_multiple_weighted` for very small seeds and optimize for large input length / low memory (#1530) ### Distributions -- `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229) -- `Uniform` implements `TryFrom` instead of `From` for ranges (#1229) -- `Uniform` now uses Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287) +- Rename module `rand::distributions` to `rand::distr` (#1470) - Relax `Sized` bound on `Distribution for &D` (#1278) -- Explicit impl of `sample_single_inclusive` (+~20% perf) (#1289) -- Impl `DistString` for `Slice` and `Uniform` (#1315) -- Let `Standard` support all `NonZero*` types (#1332) -- Add `trait Weight`, allowing `WeightedIndex` to trap overflow (#1353) -- Rename `WeightedError` to `WeightError`, revising variants (#1382) +- Rename distribution `Standard` to `StandardUniform` (#1526) +- Remove impl of `Distribution>` for `StandardUniform` (#1526) +- Let distribution `StandardUniform` support all `NonZero*` types (#1332) +- Fns `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229) +- Distribution `Uniform` implements `TryFrom` instead of `From` for ranges (#1229) +- Optimize distribution `Uniform`: use Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287) +- Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) +- Optimize fn `sample_single_inclusive` for floats (+~20% perf) (#1289) +- Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) +- Add impl `DistString` for distributions `Slice` and `Uniform` (#1315) +- Add fn `Slice::num_choices` (#1402) +- Fix portability of distribution `Slice` (#1469) +- Add trait `Weight`, allowing `WeightedIndex` to trap overflow (#1353) +- Add fns `weight, weights, total_weight` to distribution `WeightedIndex` (#1420) +- Rename enum `WeightedError` to `WeightError`, revising variants (#1382) and mark as `#[non_exhaustive]` (#1480) +- Add fn `p()` for distribution `Bernoulli` to access probability (#1481) ### SIMD - Switch to `std::simd`, expand SIMD & docs (#1239) - Optimise SIMD widening multiply (#1247) -### Other -- Bump MSRV to 1.60.0 (#1207, #1246, #1269, #1341) -- Improve `thread_rng` related docs (#1257) +### Documentation - Add `Cargo.lock.msrv` file (#1275) - Docs: enable experimental `--generate-link-to-definition` feature (#1327) -- Use `zerocopy` to replace some `unsafe` code (#1349) -- Support `std` feature without `getrandom` or `rand_chacha` (#1354) +- Better doc of crate features, use `doc_auto_cfg` (#1411, #1450) + +### Other +- Reformat with `rustfmt` and enforce (#1448) +- Apply Clippy suggestions and enforce (#1448, #1474) +- Move all benchmarks to new `benches` crate (#1329, #1439) and migrate to Criterion (#1490) ## [0.8.5] - 2021-08-20 ### Fixes diff --git a/Cargo.lock.msrv b/Cargo.lock.msrv index 36b9ea50d09..66921820c1e 100644 --- a/Cargo.lock.msrv +++ b/Cargo.lock.msrv @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0-alpha.1" +version = "0.9.0-beta.0" dependencies = [ "bincode", "log", @@ -391,7 +391,7 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.9.0-alpha.1" +version = "0.9.0-beta.0" dependencies = [ "ppv-lite86", "rand_core", @@ -401,7 +401,7 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.0-alpha.1" +version = "0.9.0-beta.0" dependencies = [ "getrandom", "serde", @@ -410,7 +410,7 @@ dependencies = [ [[package]] name = "rand_distr" -version = "0.5.0-alpha.1" +version = "0.5.0-beta.0" dependencies = [ "average", "num-traits", @@ -423,7 +423,7 @@ dependencies = [ [[package]] name = "rand_pcg" -version = "0.9.0-alpha.1" +version = "0.9.0-beta.0" dependencies = [ "bincode", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index b5b254ba2a9..e11e7b1c427 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.9.0-alpha.1" +version = "0.9.0-beta.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -69,14 +69,14 @@ members = [ exclude = ["benches", "distr_test"] [dependencies] -rand_core = { path = "rand_core", version = "=0.9.0-alpha.1", default-features = false } +rand_core = { path = "rand_core", version = "=0.9.0-beta.0", default-features = false } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } -rand_chacha = { path = "rand_chacha", version = "=0.9.0-alpha.1", default-features = false, optional = true } +rand_chacha = { path = "rand_chacha", version = "=0.9.0-beta.0", default-features = false, optional = true } zerocopy = { version = "0.8.0", default-features = false, features = ["simd"] } [dev-dependencies] -rand_pcg = { path = "rand_pcg", version = "=0.9.0-alpha.1" } +rand_pcg = { path = "rand_pcg", version = "=0.9.0-beta.0" } # Only to test serde bincode = "1.2.1" rayon = "1.7" diff --git a/README.md b/README.md index 0ac3aaac18c..dc8fc78016a 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,17 @@ Documentation: ## Usage Add this to your `Cargo.toml`: - ```toml [dependencies] rand = "0.8.5" ``` +Or, to try the 0.9.0 beta release: +```toml +[dependencies] +rand = "=0.9.0-beta.0" +``` + To get started using Rand, see [The Book](https://rust-random.github.io/book). ## Versions diff --git a/benches/benches/seq_choose.rs b/benches/benches/seq_choose.rs index a664b915016..8f19caf7e26 100644 --- a/benches/benches/seq_choose.rs +++ b/benches/benches/seq_choose.rs @@ -52,6 +52,33 @@ pub fn bench(c: &mut Criterion) { }); } + let lens = [(1, 1000), (950, 1000), (10, 100), (90, 100)]; + for (amount, len) in lens { + let name = format!("seq_slice_choose_multiple_weighted_{}_of_{}", amount, len); + c.bench_function(name.as_str(), |b| { + let mut rng = Pcg32::from_rng(&mut rand::rng()); + let mut buf = [0i32; 1000]; + rng.fill(&mut buf); + let x = black_box(&buf[..len]); + + let mut results_buf = [0i32; 950]; + let y = black_box(&mut results_buf[..amount]); + let amount = black_box(amount); + + b.iter(|| { + // Collect full result to prevent unwanted shortcuts getting + // first element (in case sample_indices returns an iterator). + let samples_iter = x + .choose_multiple_weighted(&mut rng, amount, |_| 1.0) + .unwrap(); + for (slot, sample) in y.iter_mut().zip(samples_iter) { + *slot = *sample; + } + y[amount - 1] + }) + }); + } + c.bench_function("seq_iter_choose_multiple_10_of_100", |b| { let mut rng = Pcg32::from_rng(&mut rand::rng()); let mut buf = [0i32; 100]; diff --git a/distr_test/Cargo.toml b/distr_test/Cargo.toml index 36314b37cf0..4f85d5b9f74 100644 --- a/distr_test/Cargo.toml +++ b/distr_test/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" publish = false [dev-dependencies] -rand_distr = { path = "../rand_distr", version = "=0.5.0-alpha.1", default-features = false, features = ["alloc"] } -rand = { path = "..", version = "=0.9.0-alpha.1", features = ["small_rng"] } +rand_distr = { path = "../rand_distr", version = "=0.5.0-beta.0", default-features = false, features = ["alloc"] } +rand = { path = "..", version = "=0.9.0-beta.0", features = ["small_rng"] } num-traits = "0.2.19" # Special functions for testing distributions special = "0.11.0" diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index 68dba7dfdc2..af8aa1f9ecb 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,17 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] -- The `serde1` feature has been renamed `serde` (#1477) -- Rename feature `getrandom` to `os_rng` - -## [0.9.0-alpha.1] - 2024-03-18 - -## [0.9.0-alpha.0] - 2024-02-18 -This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). +## [0.9.0-beta.0] - 2024-11-25 +This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). - Made `rand_chacha` propagate the `std` feature down to `rand_core` (#1153) - Remove usage of `unsafe` in `fn generate` (#1181) then optimise for AVX2 (~4-7%) (#1192) +- The `serde1` feature has been renamed `serde` (#1477) +- Revise crate docs (#1454) +- Rename feature `getrandom` to `os_rng` (#1537) ## [0.3.1] - 2021-06-09 - add getters corresponding to existing setters: `get_seed`, `get_stream` (#1124) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 168b39def57..096d77c9b7d 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_chacha" -version = "0.9.0-alpha.1" +version = "0.9.0-beta.0" authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -20,14 +20,14 @@ all-features = true rustdoc-args = ["--generate-link-to-definition"] [dependencies] -rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1" } +rand_core = { path = "../rand_core", version = "=0.9.0-beta.0" } ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] # Only to test serde serde_json = "1.0" -rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["os_rng"] } +rand_core = { path = "../rand_core", version = "=0.9.0-beta.0", features = ["os_rng"] } [features] default = ["std"] diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index a2d60c2da11..b22a6ea34cb 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,21 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] -- Bump the MSRV to 1.63.0 -- The `serde1` feature has been renamed `serde` (#1477) -- Rename feature `getrandom` to `os_rng` - -## [0.9.0-alpha.1] - 2024-03-18 +## [0.9.0-beta.0] - 2024-11-25 +This is a pre-release. To depend on this version, use `rand_core = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). -## [0.9.0-alpha.0] - 2024-02-18 -This is a pre-release. To depend on this version, use `rand_core = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). +### Compilation options and unsafe +- Bump the MSRV to 1.63.0 (#1207, #1246, #1269, #1341, #1416, #1536); note that 1.60.0 may work for dependents when using `--ignore-rust-version` +- The `serde1` feature has been renamed `serde` (#1477) +- Use `zerocopy` to replace some `unsafe` code (#1349, #1393, #1446, #1502) -- Bump MSRV to 1.60.0 (#1207, #1246, #1269, #1341) +### Other - Allow `rand_core::impls::fill_via_u*_chunks` to mutate source (#1182) -- Add `fn RngCore::read_adapter` implementing `std::io::Read` (#1267) -- Add `trait CryptoBlockRng: BlockRngCore`; make `trait CryptoRng: RngCore` (#1273) -- Use `zerocopy` to replace some `unsafe` code (#1349, #1393) +- Add fn `RngCore::read_adapter` implementing `std::io::Read` (#1267) +- Add trait `CryptoBlockRng: BlockRngCore`; make `trait CryptoRng: RngCore` (#1273) +- Add traits `TryRngCore`, `TryCryptoRng` (#1424, #1499) +- Add bounds `Clone` and `AsRef` to associated type `SeedableRng::Seed` (#1491) +- Rename feature `getrandom` to `os_rng` (#1537) ## [0.6.4] - 2022-09-15 - Fix unsoundness in `::next_u32` (#1160) diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 6f11b895a30..5cf21b32a1e 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.9.0-alpha.1" +version = "0.9.0-beta.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 81b62a1f28e..b0e1540d1ff 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,44 +4,47 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [0.5.0-beta.0] - 2024-11-25 +This is a pre-release. To depend on this version, use `rand = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). + +### Compilation options +- Target `rand` version `0.9.0-beta.0` +- Bump the MSRV to 1.61.0 (#1207, #1246, #1269, #1341, #1416); note that 1.60.0 may work for dependents when using `--ignore-rust-version` - The `serde1` feature has been renamed `serde` (#1477) + +### Testing +- Add Kolmogorov Smirnov tests for distributions (#1494, #1504, #1525, #1530) + +### Fixes +- Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda (#1284) +- Fix `Poisson` distribution instantiation so it return an error if lambda is infinite (#1291) +- Fix Dirichlet sample for small alpha values to avoid NaN samples (#1209) +- Fix infinite loop in `Binomial` distribution (#1325) - Fix panic in Binomial (#1484) -- Move some of the computations in Binomial from `sample` to `new` (#1484) -- Add Kolmogorov Smirnov test for sampling of `Normal` and `Binomial` (#1494) -- Add Kolmogorov Smirnov test for more distributions (#1504) -- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480). -- Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) - Fix bug in `Hypergeometric`, this is a Value-breaking change (#1510) -- Change parameter type of `Zipf::new`: `n` is now floating-point (#1518) - -### Added -- Add plots for `rand_distr` distributions to documentation (#1434) -- Add `PertBuilder`, fix case where mode ≅ mean (#1452) - -## [0.5.0-alpha.1] - 2024-03-18 -- Target `rand` version `0.9.0-alpha.1` - -## [0.5.0-alpha.0] - 2024-02-18 -This is a pre-release. To depend on this version, use `rand_distr = "=0.5.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). ### Additions - Make distributions comparable with `PartialEq` (#1218) -- Add `WeightedIndexTree` (#1372) +- Add `WeightedIndexTree` (#1372, #1444) ### Changes -- Target `rand` version `0.9.0-alpha.0` +### Other changes - Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) This breaks serialization compatibility with older versions. - `Dirichlet` now uses `const` generics, which means that its size is required at compile time (#1292) - The `Dirichlet::new_with_size` constructor was removed (#1292) +- Add `PertBuilder`, fix case where mode ≅ mean (#1452) +- Rename `Zeta`'s parameter `a` to `s` (#1466) +- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480) +- Remove support for usage of `isize` as a `WeightedAliasIndex` weight (#1487) +- Change parameter type of `Zipf::new`: `n` is now floating-point (#1518) -### Fixes -- Fix Knuth's method so `Poisson` doesn't return -1.0 for small lambda (#1284) -- Fix `Poisson` distribution instantiation so it return an error if lambda is infinite (#1291) -- Fix Dirichlet sample for small alpha values to avoid NaN samples (#1209) -- Fix infinite loop in `Binomial` distribution (#1325) +### Optimizations +- Move some of the computations in Binomial from `sample` to `new` (#1484) + +### Documentation +- Add plots for `rand_distr` distributions to documentation (#1434) ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 211d30c0f25..e20bcb319cc 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.5.0-alpha.1" +version = "0.5.0-beta.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -32,15 +32,15 @@ std_math = ["num-traits/std"] serde = ["dep:serde", "dep:serde_with", "rand/serde"] [dependencies] -rand = { path = "..", version = "=0.9.0-alpha.1", default-features = false } +rand = { path = "..", version = "=0.9.0-beta.0", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } serde_with = { version = ">= 3.0, <= 3.11", optional = true } [dev-dependencies] -rand_pcg = { version = "=0.9.0-alpha.1", path = "../rand_pcg" } +rand_pcg = { version = "=0.9.0-beta.0", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "=0.9.0-alpha.1", features = ["small_rng"] } +rand = { path = "..", version = "=0.9.0-beta.0", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.15", features = [ "std" ] } # Special functions for testing distributions diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index 0133909a97b..5b9cd14005c 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -4,17 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] -- The `serde1` feature has been renamed `serde` (#1477) -- Rename feature `getrandom` to `os_rng` - -## [0.9.0-alpha.1] - 2024-03-18 - -## [0.9.0-alpha.0] - 2024-02-18 -This is a pre-release. To depend on this version, use `rand_pcg = "=0.9.0-alpha.0"` to prevent automatic updates (which can be expected to include breaking changes). +## [0.9.0-beta.0] - 2024-11-25 +This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). +- The `serde1` feature has been renamed `serde` (#1477) - Add `Lcg128CmDxsm64` generator compatible with NumPy's `PCG64DXSM` (#1202) - Add examples for initializing the RNGs +- Revise crate docs (#1454) +- Rename feature `getrandom` to `os_rng` (#1537) ## [0.3.1] - 2021-06-15 - Add `advance` methods to RNGs (#1111) diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index fb4dd17b007..e490c1bc1a0 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_pcg" -version = "0.9.0-alpha.1" +version = "0.9.0-beta.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -24,7 +24,7 @@ serde = ["dep:serde"] os_rng = ["rand_core/os_rng"] [dependencies] -rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1" } +rand_core = { path = "../rand_core", version = "=0.9.0-beta.0" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] @@ -32,4 +32,4 @@ serde = { version = "1", features = ["derive"], optional = true } # deps yet, see: https://github.com/rust-lang/cargo/issues/1596 # Versions prior to 1.1.4 had incorrect minimal dependencies. bincode = { version = "1.1.4" } -rand_core = { path = "../rand_core", version = "=0.9.0-alpha.1", features = ["os_rng"] } +rand_core = { path = "../rand_core", version = "=0.9.0-beta.0", features = ["os_rng"] } diff --git a/src/distr/integer.rs b/src/distr/integer.rs index f20f34e0eb7..d0040e69e7e 100644 --- a/src/distr/integer.rs +++ b/src/distr/integer.rs @@ -187,6 +187,17 @@ mod tests { rng.sample::(StandardUniform); } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + #[test] + fn x86_integers() { + let mut rng = crate::test::rng(807); + + rng.sample::<__m128i, _>(StandardUniform); + rng.sample::<__m256i, _>(StandardUniform); + #[cfg(feature = "simd_support")] + rng.sample::<__m512i, _>(StandardUniform); + } + #[test] fn value_stability() { fn test_samples(zero: T, expected: &[T]) From b879689a6078a9d4a8d24319572b0f02662fb315 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 27 Nov 2024 16:57:40 +0000 Subject: [PATCH 435/443] Adjust GH Actions (#1538) --- .github/workflows/benches.yml | 36 ++++++++++++++++++++++++-------- .github/workflows/distr_test.yml | 35 ++++++++++++++++++++++++------- .github/workflows/test.yml | 36 ++++++++++++++++++++------------ .github/workflows/workspace.yml | 33 ----------------------------- benches/benches/distr.rs | 24 +++++++++++++-------- benches/benches/seq_choose.rs | 4 +--- benches/rustfmt.toml | 2 ++ 7 files changed, 95 insertions(+), 75 deletions(-) delete mode 100644 .github/workflows/workspace.yml create mode 100644 benches/rustfmt.toml diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index a862bd7e6c7..4be504fb67c 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -1,26 +1,44 @@ name: Benches on: + push: + branches: [ master ] + paths-ignore: + - "**.md" + - "distr_test/**" + - "examples/**" pull_request: - paths: - - ".github/workflows/benches.yml" - - "benches/**" + branches: [ master ] + paths-ignore: + - "**.md" + - "distr_test/**" + - "examples/**" + +defaults: + run: + working-directory: ./benches jobs: - benches: + clippy-fmt: + name: Check Clippy and rustfmt runs-on: ubuntu-latest - defaults: - run: - working-directory: ./benches steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly + toolchain: stable components: clippy, rustfmt - name: Rustfmt run: cargo fmt -- --check - name: Clippy run: cargo clippy --all-targets -- -D warnings - - name: Build + benches: + name: Test benchmarks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - name: Test run: RUSTFLAGS=-Dwarnings cargo test --benches diff --git a/.github/workflows/distr_test.yml b/.github/workflows/distr_test.yml index 34d632770af..ad0c0ba1e8d 100644 --- a/.github/workflows/distr_test.yml +++ b/.github/workflows/distr_test.yml @@ -1,24 +1,43 @@ name: distr_test on: + push: + branches: [ master ] + paths-ignore: + - "**.md" + - "benches/**" + - "examples/**" pull_request: - paths: - - ".github/workflows/distr_test.yml" - - "distr_test/**" + branches: [ master ] + paths-ignore: + - "**.md" + - "benches/**" + - "examples/**" + +defaults: + run: + working-directory: ./distr_test jobs: - distr_test: + clippy-fmt: + name: Check Clippy and rustfmt runs-on: ubuntu-latest - defaults: - run: - working-directory: ./distr_test steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly + toolchain: stable components: clippy, rustfmt - name: Rustfmt run: cargo fmt -- --check - name: Clippy run: cargo clippy --all-targets -- -D warnings + ks-tests: + name: Run Komogorov Smirnov tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - run: cargo test --release diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a8989c513e..d5088ddcf2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,15 +1,37 @@ -name: Tests +name: Main tests on: push: branches: [ master, '0.[0-9]+' ] + paths-ignore: + - "**.md" + - "benches/**" + - "distr_test/**" pull_request: branches: [ master, '0.[0-9]+' ] + paths-ignore: + - "**.md" + - "benches/**" + - "distr_test/**" permissions: contents: read # to fetch code (actions/checkout) jobs: + clippy-fmt: + name: Check Clippy and rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: clippy, rustfmt + - name: Check Clippy + run: cargo clippy --all --all-targets -- -D warnings + - name: Check rustfmt + run: cargo fmt --all -- --check + check-doc: name: Check doc runs-on: ubuntu-latest @@ -85,7 +107,6 @@ jobs: run: | cargo test --target ${{ matrix.target }} --features=nightly cargo test --target ${{ matrix.target }} --all-features - cargo test --target ${{ matrix.target }} --manifest-path benches/Cargo.toml --benches cargo test --target ${{ matrix.target }} --lib --tests --no-default-features - name: Test rand run: | @@ -111,17 +132,6 @@ jobs: - name: Test rand_chacha run: cargo test --target ${{ matrix.target }} --manifest-path rand_chacha/Cargo.toml --features=serde - test-ks: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install toolchain - uses: dtolnay/rust-toolchain@nightly - with: - target: x86_64-unknown-linux-gnu - - name: Test Komogorov Smirnov - run: cargo test --manifest-path distr_test/Cargo.toml --release - test-cross: runs-on: ${{ matrix.os }} strategy: diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml deleted file mode 100644 index ef92b7f479c..00000000000 --- a/.github/workflows/workspace.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Workspace - -on: - pull_request: - paths-ignore: - - README.md - - "benches/**" - push: - branches: master - paths-ignore: - - README.md - - "benches/**" - -jobs: - clippy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: 1.78.0 - components: clippy - - run: cargo clippy --all --all-targets -- -D warnings - - rustfmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - components: rustfmt - - run: cargo fmt --all -- --check diff --git a/benches/benches/distr.rs b/benches/benches/distr.rs index 94a39404437..fccfb1e0e96 100644 --- a/benches/benches/distr.rs +++ b/benches/benches/distr.rs @@ -6,11 +6,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(custom_inner_attributes)] - -// Rustfmt splits macro invocations to shorten lines; in this case longer-lines are more readable -#![rustfmt::skip] - use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use criterion_cycles_per_byte::CyclesPerByte; @@ -80,7 +75,8 @@ fn bench(c: &mut Criterion) { let distr = Normal::new(-E, PI).unwrap(); c.iter(|| { - distr.sample_iter(&mut rng) + distr + .sample_iter(&mut rng) .take(ITER_ELTS as usize) .fold(0.0, |a, r| a + r) }); @@ -121,12 +117,22 @@ fn bench(c: &mut Criterion) { let mut g = c.benchmark_group("weighted"); distr_int!(g, "i8", usize, WeightedIndex::new([1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); distr_int!(g, "u32", usize, WeightedIndex::new([1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); - distr_int!(g, "f64", usize, WeightedIndex::new([1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); + distr_int!(g, "f64", usize, WeightedIndex::new([1.0f64, 0.001, 1.0 / 3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); distr_int!(g, "large_set", usize, WeightedIndex::new((0..10000).rev().chain(1..10001)).unwrap()); distr_int!(g, "alias_method_i8", usize, WeightedAliasIndex::new(vec![1i8, 2, 3, 4, 12, 0, 2, 1]).unwrap()); distr_int!(g, "alias_method_u32", usize, WeightedAliasIndex::new(vec![1u32, 2, 3, 4, 12, 0, 2, 1]).unwrap()); - distr_int!(g, "alias_method_f64", usize, WeightedAliasIndex::new(vec![1.0f64, 0.001, 1.0/3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap()); - distr_int!(g, "alias_method_large_set", usize, WeightedAliasIndex::new((0..10000).rev().chain(1..10001).collect()).unwrap()); + distr_int!( + g, + "alias_method_f64", + usize, + WeightedAliasIndex::new(vec![1.0f64, 0.001, 1.0 / 3.0, 4.01, 0.0, 3.3, 22.0, 0.001]).unwrap() + ); + distr_int!( + g, + "alias_method_large_set", + usize, + WeightedAliasIndex::new((0..10000).rev().chain(1..10001).collect()).unwrap() + ); g.finish(); let mut g = c.benchmark_group("binomial"); diff --git a/benches/benches/seq_choose.rs b/benches/benches/seq_choose.rs index 8f19caf7e26..56223dd0a62 100644 --- a/benches/benches/seq_choose.rs +++ b/benches/benches/seq_choose.rs @@ -68,9 +68,7 @@ pub fn bench(c: &mut Criterion) { b.iter(|| { // Collect full result to prevent unwanted shortcuts getting // first element (in case sample_indices returns an iterator). - let samples_iter = x - .choose_multiple_weighted(&mut rng, amount, |_| 1.0) - .unwrap(); + let samples_iter = x.choose_multiple_weighted(&mut rng, amount, |_| 1.0).unwrap(); for (slot, sample) in y.iter_mut().zip(samples_iter) { *slot = *sample; } diff --git a/benches/rustfmt.toml b/benches/rustfmt.toml new file mode 100644 index 00000000000..b64fd7ad0e6 --- /dev/null +++ b/benches/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 120 +fn_call_width = 108 From 88c310b18939a12f407c659bdd66554677d8b8c1 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 27 Nov 2024 16:57:54 +0000 Subject: [PATCH 436/443] Fix docs.rs build options (#1539) --- Cargo.toml | 2 +- distr_test/Cargo.toml | 2 +- rand_core/Cargo.toml | 2 +- rand_distr/CHANGELOG.md | 3 +++ rand_distr/Cargo.toml | 5 +++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e11e7b1c427..bd6c2d2c4da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ include = ["src/", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] # To build locally: # RUSTDOCFLAGS="--cfg docsrs -Zunstable-options --generate-link-to-definition" cargo +nightly doc --all --all-features --no-deps --open all-features = true -rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] +rustdoc-args = ["--generate-link-to-definition"] [package.metadata.playground] features = ["small_rng", "serde"] diff --git a/distr_test/Cargo.toml b/distr_test/Cargo.toml index 4f85d5b9f74..801b0c2e81a 100644 --- a/distr_test/Cargo.toml +++ b/distr_test/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" publish = false [dev-dependencies] -rand_distr = { path = "../rand_distr", version = "=0.5.0-beta.0", default-features = false, features = ["alloc"] } +rand_distr = { path = "../rand_distr", version = "=0.5.0-beta.1", default-features = false, features = ["alloc"] } rand = { path = "..", version = "=0.9.0-beta.0", features = ["small_rng"] } num-traits = "0.2.19" # Special functions for testing distributions diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 5cf21b32a1e..40fa31be029 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -19,7 +19,7 @@ rust-version = "1.63" # To build locally: # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --open all-features = true -rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] +rustdoc-args = ["--generate-link-to-definition"] [package.metadata.playground] all-features = true diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index b0e1540d1ff..6748234a587 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0-beta.1] - 2024-11-27 +- Fix docs.rs build (#1539) + ## [0.5.0-beta.0] - 2024-11-25 This is a pre-release. To depend on this version, use `rand = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index e20bcb319cc..f3433e36591 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.5.0-beta.0" +version = "0.5.0-beta.1" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -17,7 +17,8 @@ rust-version = "1.63" include = ["/src", "LICENSE-*", "README.md", "CHANGELOG.md", "COPYRIGHT"] [package.metadata.docs.rs] -rustdoc-args = ["--cfg docsrs", "--generate-link-to-definition"] +features = ["serde"] +rustdoc-args = ["--generate-link-to-definition"] [features] default = ["std"] From 9f05e22afb6031d32f36cd927592e7e49b668d64 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 30 Nov 2024 08:45:04 +0000 Subject: [PATCH 437/443] Update: getrandom v0.3.0 rc.0 (#1541) --- .github/workflows/test.yml | 2 +- CHANGELOG.md | 4 ++++ Cargo.toml | 10 +++++----- README.md | 2 +- distr_test/Cargo.toml | 4 ++-- rand_chacha/CHANGELOG.md | 3 +++ rand_chacha/Cargo.toml | 6 +++--- rand_core/CHANGELOG.md | 3 +++ rand_core/Cargo.toml | 7 +++---- rand_core/README.md | 27 +-------------------------- rand_core/src/lib.rs | 4 +--- rand_core/src/os.rs | 21 +++------------------ rand_distr/CHANGELOG.md | 3 +++ rand_distr/Cargo.toml | 8 ++++---- rand_pcg/CHANGELOG.md | 3 +++ rand_pcg/Cargo.toml | 6 +++--- 16 files changed, 43 insertions(+), 70 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d5088ddcf2f..9858b0f41a6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,7 +121,7 @@ jobs: run: | cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features - cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features --features=alloc,os_rng + cargo test --target ${{ matrix.target }} --manifest-path rand_core/Cargo.toml --no-default-features --features=os_rng - name: Test rand_distr run: | cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --features=serde diff --git a/CHANGELOG.md b/CHANGELOG.md index 974ff205511..42e5eb5a53f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [0.9.0-beta.1] - 2024-11-30 +- Bump `rand_core` version + ## [0.9.0-beta.0] - 2024-11-25 This is a pre-release. To depend on this version, use `rand = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). @@ -65,6 +68,7 @@ This is a pre-release. To depend on this version, use `rand = "=0.9.0-beta.0"` t - Distribution `Uniform` implements `TryFrom` instead of `From` for ranges (#1229) - Optimize distribution `Uniform`: use Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287) - Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) +- Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` (except via `UniformUsize`) and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Optimize fn `sample_single_inclusive` for floats (+~20% perf) (#1289) - Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) - Add impl `DistString` for distributions `Slice` and `Uniform` (#1315) diff --git a/Cargo.toml b/Cargo.toml index bd6c2d2c4da..ac47d4a1256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.9.0-beta.0" +version = "0.9.0-beta.1" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -37,7 +37,7 @@ serde = ["dep:serde", "rand_core/serde"] std = ["rand_core/std", "rand_chacha?/std", "alloc"] # Option: "alloc" enables support for Vec and Box when not using "std" -alloc = ["rand_core/alloc"] +alloc = [] # Option: enable OsRng os_rng = ["rand_core/os_rng"] @@ -69,14 +69,14 @@ members = [ exclude = ["benches", "distr_test"] [dependencies] -rand_core = { path = "rand_core", version = "=0.9.0-beta.0", default-features = false } +rand_core = { path = "rand_core", version = "=0.9.0-beta.1", default-features = false } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } -rand_chacha = { path = "rand_chacha", version = "=0.9.0-beta.0", default-features = false, optional = true } +rand_chacha = { path = "rand_chacha", version = "=0.9.0-beta.1", default-features = false, optional = true } zerocopy = { version = "0.8.0", default-features = false, features = ["simd"] } [dev-dependencies] -rand_pcg = { path = "rand_pcg", version = "=0.9.0-beta.0" } +rand_pcg = { path = "rand_pcg", version = "=0.9.0-beta.1" } # Only to test serde bincode = "1.2.1" rayon = "1.7" diff --git a/README.md b/README.md index dc8fc78016a..e633ef3383e 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ rand = "0.8.5" Or, to try the 0.9.0 beta release: ```toml [dependencies] -rand = "=0.9.0-beta.0" +rand = "=0.9.0-beta.1" ``` To get started using Rand, see [The Book](https://rust-random.github.io/book). diff --git a/distr_test/Cargo.toml b/distr_test/Cargo.toml index 801b0c2e81a..2092595998a 100644 --- a/distr_test/Cargo.toml +++ b/distr_test/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" publish = false [dev-dependencies] -rand_distr = { path = "../rand_distr", version = "=0.5.0-beta.1", default-features = false, features = ["alloc"] } -rand = { path = "..", version = "=0.9.0-beta.0", features = ["small_rng"] } +rand_distr = { path = "../rand_distr", version = "=0.5.0-beta.2", default-features = false, features = ["alloc"] } +rand = { path = "..", version = "=0.9.0-beta.1", features = ["small_rng"] } num-traits = "0.2.19" # Special functions for testing distributions special = "0.11.0" diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index af8aa1f9ecb..4f33c2cde71 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0-beta.1] - 2024-11-30 +- Bump `rand_core` version + ## [0.9.0-beta.0] - 2024-11-25 This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 096d77c9b7d..6438e833c51 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_chacha" -version = "0.9.0-beta.0" +version = "0.9.0-beta.1" authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -20,14 +20,14 @@ all-features = true rustdoc-args = ["--generate-link-to-definition"] [dependencies] -rand_core = { path = "../rand_core", version = "=0.9.0-beta.0" } +rand_core = { path = "../rand_core", version = "=0.9.0-beta.1" } ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] # Only to test serde serde_json = "1.0" -rand_core = { path = "../rand_core", version = "=0.9.0-beta.0", features = ["os_rng"] } +rand_core = { path = "../rand_core", version = "=0.9.0-beta.1", features = ["os_rng"] } [features] default = ["std"] diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index b22a6ea34cb..327a98c095e 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0-beta.1] - 2024-11-30 +- Update to `getrandom` v0.3.0-rc.0 + ## [0.9.0-beta.0] - 2024-11-25 This is a pre-release. To depend on this version, use `rand_core = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index 40fa31be029..c932b9b88fe 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.9.0-beta.0" +version = "0.9.0-beta.1" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -25,12 +25,11 @@ rustdoc-args = ["--generate-link-to-definition"] all-features = true [features] -std = ["alloc", "getrandom?/std"] -alloc = [] # enables Vec and Box support without std +std = ["getrandom?/std"] os_rng = ["dep:getrandom"] serde = ["dep:serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } -getrandom = { version = "0.2", optional = true } +getrandom = { version = "0.3.0-rc.0", optional = true } zerocopy = { version = "0.8.0", default-features = false } diff --git a/rand_core/README.md b/rand_core/README.md index 98fb7c9a79c..3a3584255a4 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -42,34 +42,9 @@ The traits and error types are also available via `rand`. The current version is: ``` -rand_core = "0.6.4" +rand_core = "=0.9.0-beta.1" ``` -Rand libs have inter-dependencies and make use of the -[semver trick](https://github.com/dtolnay/semver-trick/) in order to make traits -compatible across crate versions. (This is especially important for `RngCore` -and `SeedableRng`.) A few crate releases are thus compatibility shims, -depending on the *next* lib version (e.g. `rand_core` versions `0.2.2` and -`0.3.1`). This means, for example, that `rand_core_0_4_0::SeedableRng` and -`rand_core_0_3_0::SeedableRng` are distinct, incompatible traits, which can -cause build errors. Usually, running `cargo update` is enough to fix any issues. - -## Crate Features - -`rand_core` supports `no_std` and `alloc`-only configurations, as well as full -`std` functionality. The differences between `no_std` and full `std` are small, -comprising `RngCore` support for `Box` types where `R: RngCore`, -`std::io::Read` support for types supporting `RngCore`, and -extensions to the `Error` type's functionality. - -The `std` feature is *not enabled by default*. This is primarily to avoid build -problems where one crate implicitly requires `rand_core` with `std` support and -another crate requires `rand` *without* `std` support. However, the `rand` crate -continues to enable `std` support by default, both for itself and `rand_core`. - -The `serde` feature can be used to derive `Serialize` and `Deserialize` for RNG -implementations that use the `BlockRng` or `BlockRng64` wrappers. - # License diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index adc71770812..babc0e349aa 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -35,8 +35,6 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![no_std] -#[cfg(feature = "alloc")] -extern crate alloc; #[cfg(feature = "std")] extern crate std; @@ -512,7 +510,7 @@ pub trait SeedableRng: Sized { #[cfg(feature = "os_rng")] fn try_from_os_rng() -> Result { let mut seed = Self::Seed::default(); - getrandom::getrandom(seed.as_mut())?; + getrandom::fill(seed.as_mut())?; let res = Self::from_seed(seed); Ok(res) } diff --git a/rand_core/src/os.rs b/rand_core/src/os.rs index 44efe700c13..49111632d9f 100644 --- a/rand_core/src/os.rs +++ b/rand_core/src/os.rs @@ -9,7 +9,6 @@ //! Interface to the random number generator of the operating system. use crate::{TryCryptoRng, TryRngCore}; -use getrandom::getrandom; /// An interface over the operating-system's random data source /// @@ -79,15 +78,6 @@ impl OsError { pub fn raw_os_error(self) -> Option { self.0.raw_os_error() } - - /// Extract the bare error code. - /// - /// This code can either come from the underlying OS, or be a custom error. - /// Use [`OsError::raw_os_error()`] to disambiguate. - #[inline] - pub const fn code(self) -> core::num::NonZeroU32 { - self.0.code() - } } impl TryRngCore for OsRng { @@ -95,22 +85,17 @@ impl TryRngCore for OsRng { #[inline] fn try_next_u32(&mut self) -> Result { - let mut buf = [0u8; 4]; - getrandom(&mut buf).map_err(OsError)?; - Ok(u32::from_ne_bytes(buf)) + getrandom::u32().map_err(OsError) } #[inline] fn try_next_u64(&mut self) -> Result { - let mut buf = [0u8; 8]; - getrandom(&mut buf).map_err(OsError)?; - Ok(u64::from_ne_bytes(buf)) + getrandom::u64().map_err(OsError) } #[inline] fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> { - getrandom(dest).map_err(OsError)?; - Ok(()) + getrandom::fill(dest).map_err(OsError) } } diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 6748234a587..a4ee5fb0221 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0-beta.2] - 2024-11-30 +- Bump `rand` version + ## [0.5.0-beta.1] - 2024-11-27 - Fix docs.rs build (#1539) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index f3433e36591..3d7477518af 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.5.0-beta.1" +version = "0.5.0-beta.2" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -33,15 +33,15 @@ std_math = ["num-traits/std"] serde = ["dep:serde", "dep:serde_with", "rand/serde"] [dependencies] -rand = { path = "..", version = "=0.9.0-beta.0", default-features = false } +rand = { path = "..", version = "=0.9.0-beta.1", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } serde_with = { version = ">= 3.0, <= 3.11", optional = true } [dev-dependencies] -rand_pcg = { version = "=0.9.0-beta.0", path = "../rand_pcg" } +rand_pcg = { version = "=0.9.0-beta.1", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "=0.9.0-beta.0", features = ["small_rng"] } +rand = { path = "..", version = "=0.9.0-beta.1", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.15", features = [ "std" ] } # Special functions for testing distributions diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index 5b9cd14005c..f3e50819c25 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0-beta.1] - 2024-11-30 +- Bump `rand_core` version + ## [0.9.0-beta.0] - 2024-11-25 This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index e490c1bc1a0..3ba620a7a63 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_pcg" -version = "0.9.0-beta.0" +version = "0.9.0-beta.1" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -24,7 +24,7 @@ serde = ["dep:serde"] os_rng = ["rand_core/os_rng"] [dependencies] -rand_core = { path = "../rand_core", version = "=0.9.0-beta.0" } +rand_core = { path = "../rand_core", version = "=0.9.0-beta.1" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] @@ -32,4 +32,4 @@ serde = { version = "1", features = ["derive"], optional = true } # deps yet, see: https://github.com/rust-lang/cargo/issues/1596 # Versions prior to 1.1.4 had incorrect minimal dependencies. bincode = { version = "1.1.4" } -rand_core = { path = "../rand_core", version = "=0.9.0-beta.0", features = ["os_rng"] } +rand_core = { path = "../rand_core", version = "=0.9.0-beta.1", features = ["os_rng"] } From c681dfc345b3f24852a3931d3ba3adda2356336d Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 19 Dec 2024 08:58:56 +0000 Subject: [PATCH 438/443] Create FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..3e0dd6b4f5b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: dhardy From afa24e49b418fb06d8f030b15636f12814ce13a5 Mon Sep 17 00:00:00 2001 From: Alex Touchet <26315797+atouchet@users.noreply.github.com> Date: Thu, 26 Dec 2024 10:44:31 -0800 Subject: [PATCH 439/443] Fix test status badges (#1544) --- README.md | 2 +- rand_chacha/README.md | 2 +- rand_core/README.md | 2 +- rand_distr/README.md | 2 +- rand_pcg/README.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e633ef3383e..2758297d0b9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rand -[![Test Status](https://github.com/rust-random/rand/workflows/Tests/badge.svg?event=push)](https://github.com/rust-random/rand/actions) +[![Test Status](https://github.com/rust-random/rand/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/rust-random/rand/actions) [![Crate](https://img.shields.io/crates/v/rand.svg)](https://crates.io/crates/rand) [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand) diff --git a/rand_chacha/README.md b/rand_chacha/README.md index 35a49a366d0..167417f85c8 100644 --- a/rand_chacha/README.md +++ b/rand_chacha/README.md @@ -1,6 +1,6 @@ # rand_chacha -[![Test Status](https://github.com/rust-random/rand/workflows/Tests/badge.svg?event=push)](https://github.com/rust-random/rand/actions) +[![Test Status](https://github.com/rust-random/rand/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/rust-random/rand/actions) [![Latest version](https://img.shields.io/crates/v/rand_chacha.svg)](https://crates.io/crates/rand_chacha) [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_chacha) diff --git a/rand_core/README.md b/rand_core/README.md index 3a3584255a4..b95287c4e70 100644 --- a/rand_core/README.md +++ b/rand_core/README.md @@ -1,6 +1,6 @@ # rand_core -[![Test Status](https://github.com/rust-random/rand/workflows/Tests/badge.svg?event=push)](https://github.com/rust-random/rand/actions) +[![Test Status](https://github.com/rust-random/rand/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/rust-random/rand/actions) [![Latest version](https://img.shields.io/crates/v/rand_core.svg)](https://crates.io/crates/rand_core) [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_core) diff --git a/rand_distr/README.md b/rand_distr/README.md index 78d26b29a73..193d54123d1 100644 --- a/rand_distr/README.md +++ b/rand_distr/README.md @@ -1,6 +1,6 @@ # rand_distr -[![Test Status](https://github.com/rust-random/rand/workflows/Tests/badge.svg?event=push)](https://github.com/rust-random/rand/actions) +[![Test Status](https://github.com/rust-random/rand/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/rust-random/rand/actions) [![Latest version](https://img.shields.io/crates/v/rand_distr.svg)](https://crates.io/crates/rand_distr) [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_distr) diff --git a/rand_pcg/README.md b/rand_pcg/README.md index 79535b8cda3..50e91e59795 100644 --- a/rand_pcg/README.md +++ b/rand_pcg/README.md @@ -1,6 +1,6 @@ # rand_pcg -[![Test Status](https://github.com/rust-random/rand/workflows/Tests/badge.svg?event=push)](https://github.com/rust-random/rand/actions) +[![Test Status](https://github.com/rust-random/rand/actions/workflows/test.yml/badge.svg?event=push)](https://github.com/rust-random/rand/actions) [![Latest version](https://img.shields.io/crates/v/rand_pcg.svg)](https://crates.io/crates/rand_pcg) [![Book](https://img.shields.io/badge/book-master-yellow.svg)](https://rust-random.github.io/book/) [![API](https://img.shields.io/badge/api-master-yellow.svg)](https://rust-random.github.io/rand/rand_pcg) From 16eb7de94a124e84c11b0cb236c8dc798fe5cd25 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 9 Jan 2025 08:08:09 +0000 Subject: [PATCH 440/443] Add the `thread_rng` feature flag (#1547) --- CHANGELOG.md | 3 +++ Cargo.toml | 7 +++++-- README.md | 2 +- distr_test/Cargo.toml | 4 ++-- rand_distr/CHANGELOG.md | 3 +++ rand_distr/Cargo.toml | 6 +++--- src/lib.rs | 22 +++++++++++----------- src/prelude.rs | 2 +- src/rngs/mod.rs | 4 ++-- 9 files changed, 31 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e5eb5a53f..bc9ecdf6034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. +## [0.9.0-beta.3] - 2025-01-03 +- Add feature `thread_rng` (#1547) + ## [0.9.0-beta.1] - 2024-11-30 - Bump `rand_core` version diff --git a/Cargo.toml b/Cargo.toml index ac47d4a1256..d8a7e77c72c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.9.0-beta.1" +version = "0.9.0-beta.3" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -28,7 +28,7 @@ features = ["small_rng", "serde"] [features] # Meta-features: -default = ["std", "std_rng", "os_rng", "small_rng"] +default = ["std", "std_rng", "os_rng", "small_rng", "thread_rng"] nightly = [] # some additions requiring nightly Rust serde = ["dep:serde", "rand_core/serde"] @@ -51,6 +51,9 @@ std_rng = ["dep:rand_chacha"] # Option: enable SmallRng small_rng = [] +# Option: enable ThreadRng and rng() +thread_rng = ["std", "std_rng", "os_rng"] + # Option: use unbiased sampling for algorithms supporting this option: Uniform distribution. # By default, bias affecting no more than one in 2^48 samples is accepted. # Note: enabling this option is expected to affect reproducibility of results. diff --git a/README.md b/README.md index 2758297d0b9..524c2e8047a 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ rand = "0.8.5" Or, to try the 0.9.0 beta release: ```toml [dependencies] -rand = "=0.9.0-beta.1" +rand = "=0.9.0-beta.3" ``` To get started using Rand, see [The Book](https://rust-random.github.io/book). diff --git a/distr_test/Cargo.toml b/distr_test/Cargo.toml index 2092595998a..54b30e754b1 100644 --- a/distr_test/Cargo.toml +++ b/distr_test/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" publish = false [dev-dependencies] -rand_distr = { path = "../rand_distr", version = "=0.5.0-beta.2", default-features = false, features = ["alloc"] } -rand = { path = "..", version = "=0.9.0-beta.1", features = ["small_rng"] } +rand_distr = { path = "../rand_distr", version = "=0.5.0-beta.3", default-features = false, features = ["alloc"] } +rand = { path = "..", version = "=0.9.0-beta.3", features = ["small_rng"] } num-traits = "0.2.19" # Special functions for testing distributions special = "0.11.0" diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index a4ee5fb0221..155b5ce8452 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.0-beta.3] - 2025-01-03 +- Bump `rand` version (#1547) + ## [0.5.0-beta.2] - 2024-11-30 - Bump `rand` version diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 3d7477518af..7ba52f95046 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.5.0-beta.2" +version = "0.5.0-beta.3" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -33,7 +33,7 @@ std_math = ["num-traits/std"] serde = ["dep:serde", "dep:serde_with", "rand/serde"] [dependencies] -rand = { path = "..", version = "=0.9.0-beta.1", default-features = false } +rand = { path = "..", version = "=0.9.0-beta.3", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } serde_with = { version = ">= 3.0, <= 3.11", optional = true } @@ -41,7 +41,7 @@ serde_with = { version = ">= 3.0, <= 3.11", optional = true } [dev-dependencies] rand_pcg = { version = "=0.9.0-beta.1", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "=0.9.0-beta.1", features = ["small_rng"] } +rand = { path = "..", version = "=0.9.0-beta.3", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.15", features = [ "std" ] } # Special functions for testing distributions diff --git a/src/lib.rs b/src/lib.rs index 192605a5021..b5bb4fcb2f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,13 +103,13 @@ pub mod rngs; pub mod seq; // Public exports -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] pub use crate::rngs::thread::rng; /// Access the thread-local generator /// /// Use [`rand::rng()`](rng()) instead. -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] #[deprecated(since = "0.9.0", note = "renamed to `rng`")] #[inline] pub fn thread_rng() -> crate::rngs::ThreadRng { @@ -118,7 +118,7 @@ pub fn thread_rng() -> crate::rngs::ThreadRng { pub use rng::{Fill, Rng}; -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] use crate::distr::{Distribution, StandardUniform}; /// Generate a random value using the thread-local random number generator. @@ -159,7 +159,7 @@ use crate::distr::{Distribution, StandardUniform}; /// /// [`StandardUniform`]: distr::StandardUniform /// [`ThreadRng`]: rngs::ThreadRng -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] #[inline] pub fn random() -> T where @@ -179,7 +179,7 @@ where /// let v: Vec = rand::random_iter().take(5).collect(); /// println!("{v:?}"); /// ``` -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] #[inline] pub fn random_iter() -> distr::DistIter where @@ -204,7 +204,7 @@ where /// ``` /// Note that the first example can also be achieved (without `collect`'ing /// to a `Vec`) using [`seq::IteratorRandom::choose`]. -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] #[inline] pub fn random_range(range: R) -> T where @@ -228,7 +228,7 @@ where /// # Panics /// /// If `p < 0` or `p > 1`. -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] #[inline] #[track_caller] pub fn random_bool(p: f64) -> bool { @@ -260,7 +260,7 @@ pub fn random_bool(p: f64) -> bool { /// ``` /// /// [`Bernoulli`]: distr::Bernoulli -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] #[inline] #[track_caller] pub fn random_ratio(numerator: u32, denominator: u32) -> bool { @@ -282,7 +282,7 @@ pub fn random_ratio(numerator: u32, denominator: u32) -> bool { /// Note that you can instead use [`random()`] to generate an array of random /// data, though this is slower for small elements (smaller than the RNG word /// size). -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] #[inline] #[track_caller] pub fn fill(dest: &mut T) { @@ -302,7 +302,7 @@ mod test { } #[test] - #[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] + #[cfg(feature = "thread_rng")] fn test_random() { let _n: u64 = random(); let _f: f32 = random(); @@ -316,7 +316,7 @@ mod test { } #[test] - #[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] + #[cfg(feature = "thread_rng")] fn test_range() { let _n: usize = random_range(42..=43); let _f: f32 = random_range(42.0..43.0); diff --git a/src/prelude.rs b/src/prelude.rs index 0e15855c928..b0f563ad5fc 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -27,7 +27,7 @@ pub use crate::rngs::SmallRng; #[doc(no_inline)] pub use crate::rngs::StdRng; #[doc(no_inline)] -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] pub use crate::rngs::ThreadRng; #[doc(no_inline)] pub use crate::seq::{IndexedMutRandom, IndexedRandom, IteratorRandom, SliceRandom}; diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index 32e12d68892..cb7ed57f33e 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -95,14 +95,14 @@ mod xoshiro256plusplus; #[cfg(feature = "std_rng")] mod std; -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] pub(crate) mod thread; #[cfg(feature = "small_rng")] pub use self::small::SmallRng; #[cfg(feature = "std_rng")] pub use self::std::StdRng; -#[cfg(all(feature = "std", feature = "std_rng", feature = "os_rng"))] +#[cfg(feature = "thread_rng")] pub use self::thread::ThreadRng; #[cfg(feature = "os_rng")] From b4b1eb7579c0a47c1d71560ada0acffd647c9370 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 14 Jan 2025 07:37:46 +0000 Subject: [PATCH 441/443] Re-org with distr::slice, distr::weighted modules (#1548) - Move `Slice` -> `slice::Choose`, `EmptySlice` -> `slice::Empty` - Rename trait `DistString` -> `SampleString` - Rename `DistIter` -> `Iter`, `DistMap` -> `Map` - Move `{Weight, WeightError, WeightedIndex}` -> `weighted::{Weight, Error, WeightedIndex}` - Move `weighted_alias::{AliasableWeight, WeightedAliasIndex}` -> `weighted::{..}` - Move `weighted_tree::WeightedTreeIndex` -> `weighted::WeightedTreeIndex` --- .github/workflows/benches.yml | 4 +- .github/workflows/distr_test.yml | 4 +- .github/workflows/test.yml | 2 +- CHANGELOG.md | 4 + benches/benches/distr.rs | 1 + benches/benches/weighted.rs | 2 +- distr_test/tests/weighted.rs | 4 +- rand_distr/CHANGELOG.md | 6 + rand_distr/src/lib.rs | 24 +-- rand_distr/src/weighted/mod.rs | 28 +++ .../src/{ => weighted}/weighted_alias.rs | 42 ++-- .../src/{ => weighted}/weighted_tree.rs | 61 +++--- src/distr/distribution.rs | 58 +++--- src/distr/mod.rs | 16 +- src/distr/other.rs | 14 +- src/distr/slice.rs | 82 ++++---- src/distr/uniform_other.rs | 7 +- src/distr/weighted/mod.rs | 115 +++++++++++ src/distr/{ => weighted}/weighted_index.rs | 190 ++++-------------- src/lib.rs | 2 +- src/rng.rs | 4 +- src/seq/mod.rs | 4 +- src/seq/slice.rs | 14 +- 23 files changed, 354 insertions(+), 334 deletions(-) create mode 100644 rand_distr/src/weighted/mod.rs rename rand_distr/src/{ => weighted}/weighted_alias.rs (94%) rename rand_distr/src/{ => weighted}/weighted_tree.rs (87%) create mode 100644 src/distr/weighted/mod.rs rename src/distr/{ => weighted}/weighted_index.rs (77%) diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml index 4be504fb67c..22b4baa8dce 100644 --- a/.github/workflows/benches.yml +++ b/.github/workflows/benches.yml @@ -20,7 +20,7 @@ defaults: jobs: clippy-fmt: - name: Check Clippy and rustfmt + name: "Benches: Check Clippy and rustfmt" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: - name: Clippy run: cargo clippy --all-targets -- -D warnings benches: - name: Test benchmarks + name: "Benches: Test" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/distr_test.yml b/.github/workflows/distr_test.yml index ad0c0ba1e8d..f2b7f814c98 100644 --- a/.github/workflows/distr_test.yml +++ b/.github/workflows/distr_test.yml @@ -20,7 +20,7 @@ defaults: jobs: clippy-fmt: - name: Check Clippy and rustfmt + name: "distr_test: Check Clippy and rustfmt" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: - name: Clippy run: cargo clippy --all-targets -- -D warnings ks-tests: - name: Run Komogorov Smirnov tests + name: "distr_test: Run Komogorov Smirnov tests" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9858b0f41a6..293d5f4942d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: toolchain: stable components: clippy, rustfmt - name: Check Clippy - run: cargo clippy --all --all-targets -- -D warnings + run: cargo clippy --workspace -- -D warnings - name: Check rustfmt run: cargo fmt --all -- --check diff --git a/CHANGELOG.md b/CHANGELOG.md index bc9ecdf6034..8f2e62e9bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. ## [0.9.0-beta.3] - 2025-01-03 - Add feature `thread_rng` (#1547) +- Move `distr::Slice` -> `distr::slice::Choose`, `distr::EmptySlice` -> `distr::slice::Empty` (#1548) +- Rename trait `distr::DistString` -> `distr::SampleString` (#1548) +- Rename `distr::DistIter` -> `distr::Iter`, `distr::DistMap` -> `distr::Map` (#1548) +- Move `distr::{Weight, WeightError, WeightedIndex}` -> `distr::weighted::{Weight, Error, WeightedIndex}` (#1548) ## [0.9.0-beta.1] - 2024-11-30 - Bump `rand_core` version diff --git a/benches/benches/distr.rs b/benches/benches/distr.rs index fccfb1e0e96..3a76211972d 100644 --- a/benches/benches/distr.rs +++ b/benches/benches/distr.rs @@ -10,6 +10,7 @@ use criterion::{criterion_group, criterion_main, Criterion, Throughput}; use criterion_cycles_per_byte::CyclesPerByte; use rand::prelude::*; +use rand_distr::weighted::*; use rand_distr::*; // At this time, distributions are optimised for 64-bit platforms. diff --git a/benches/benches/weighted.rs b/benches/benches/weighted.rs index d7af914736b..69576b3608d 100644 --- a/benches/benches/weighted.rs +++ b/benches/benches/weighted.rs @@ -7,7 +7,7 @@ // except according to those terms. use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use rand::distr::WeightedIndex; +use rand::distr::weighted::WeightedIndex; use rand::prelude::*; use rand::seq::index::sample_weighted; diff --git a/distr_test/tests/weighted.rs b/distr_test/tests/weighted.rs index cf87b3ee634..73df7beb9bc 100644 --- a/distr_test/tests/weighted.rs +++ b/distr_test/tests/weighted.rs @@ -8,9 +8,9 @@ mod ks; use ks::test_discrete; -use rand::distr::{Distribution, WeightedIndex}; +use rand::distr::Distribution; use rand::seq::{IndexedRandom, IteratorRandom}; -use rand_distr::{WeightedAliasIndex, WeightedTreeIndex}; +use rand_distr::weighted::*; /// Takes the unnormalized pdf and creates the cdf of a discrete distribution fn make_cdf(num: usize, f: impl Fn(i64) -> f64) -> impl Fn(i64) -> f64 { diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index 155b5ce8452..ee3490ca30d 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.5.0-beta.3] - 2025-01-03 - Bump `rand` version (#1547) +- Move `Slice` -> `slice::Choose`, `EmptySlice` -> `slice::Empty` (#1548) +- Rename trait `DistString` -> `SampleString` (#1548) +- Rename `DistIter` -> `Iter`, `DistMap` -> `Map` (#1548) +- Move `{Weight, WeightError, WeightedIndex}` -> `weighted::{Weight, Error, WeightedIndex}` (#1548) +- Move `weighted_alias::{AliasableWeight, WeightedAliasIndex}` -> `weighted::{..}` (#1548) +- Move `weighted_tree::WeightedTreeIndex` -> `weighted::WeightedTreeIndex` (#1548) ## [0.5.0-beta.2] - 2024-11-30 - Bump `rand` version diff --git a/rand_distr/src/lib.rs b/rand_distr/src/lib.rs index efd316b09c0..ef1109b7d6f 100644 --- a/rand_distr/src/lib.rs +++ b/rand_distr/src/lib.rs @@ -33,9 +33,10 @@ //! //! The following are re-exported: //! -//! - The [`Distribution`] trait and [`DistIter`] helper type +//! - The [`Distribution`] trait and [`Iter`] helper type //! - The [`StandardUniform`], [`Alphanumeric`], [`Uniform`], [`OpenClosed01`], -//! [`Open01`], [`Bernoulli`], and [`WeightedIndex`] distributions +//! [`Open01`], [`Bernoulli`] distributions +//! - The [`weighted`] module //! //! ## Distributions //! @@ -76,9 +77,6 @@ //! - [`UnitBall`] distribution //! - [`UnitCircle`] distribution //! - [`UnitDisc`] distribution -//! - Alternative implementations for weighted index sampling -//! - [`WeightedAliasIndex`] distribution -//! - [`WeightedTreeIndex`] distribution //! - Misc. distributions //! - [`InverseGaussian`] distribution //! - [`NormalInverseGaussian`] distribution @@ -94,7 +92,7 @@ extern crate std; use rand::Rng; pub use rand::distr::{ - uniform, Alphanumeric, Bernoulli, BernoulliError, DistIter, Distribution, Open01, OpenClosed01, + uniform, Alphanumeric, Bernoulli, BernoulliError, Distribution, Iter, Open01, OpenClosed01, StandardUniform, Uniform, }; @@ -128,16 +126,13 @@ pub use self::unit_sphere::UnitSphere; pub use self::weibull::{Error as WeibullError, Weibull}; pub use self::zeta::{Error as ZetaError, Zeta}; pub use self::zipf::{Error as ZipfError, Zipf}; -#[cfg(feature = "alloc")] -pub use rand::distr::{WeightError, WeightedIndex}; pub use student_t::StudentT; -#[cfg(feature = "alloc")] -pub use weighted_alias::WeightedAliasIndex; -#[cfg(feature = "alloc")] -pub use weighted_tree::WeightedTreeIndex; pub use num_traits; +#[cfg(feature = "alloc")] +pub mod weighted; + #[cfg(test)] #[macro_use] mod test { @@ -189,11 +184,6 @@ mod test { } } -#[cfg(feature = "alloc")] -pub mod weighted_alias; -#[cfg(feature = "alloc")] -pub mod weighted_tree; - mod beta; mod binomial; mod cauchy; diff --git a/rand_distr/src/weighted/mod.rs b/rand_distr/src/weighted/mod.rs new file mode 100644 index 00000000000..1c54e48e69c --- /dev/null +++ b/rand_distr/src/weighted/mod.rs @@ -0,0 +1,28 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Weighted (index) sampling +//! +//! This module is a superset of [`rand::distr::weighted`]. +//! +//! Multiple implementations of weighted index sampling are provided: +//! +//! - [`WeightedIndex`] (a re-export from [`rand`]) supports fast construction +//! and `O(log N)` sampling over `N` weights. +//! It also supports updating weights with `O(N)` time. +//! - [`WeightedAliasIndex`] supports `O(1)` sampling, but due to high +//! construction time many samples are required to outperform [`WeightedIndex`]. +//! - [`WeightedTreeIndex`] supports `O(log N)` sampling and +//! update/insertion/removal of weights with `O(log N)` time. + +mod weighted_alias; +mod weighted_tree; + +pub use rand::distr::weighted::*; +pub use weighted_alias::*; +pub use weighted_tree::*; diff --git a/rand_distr/src/weighted_alias.rs b/rand_distr/src/weighted/weighted_alias.rs similarity index 94% rename from rand_distr/src/weighted_alias.rs rename to rand_distr/src/weighted/weighted_alias.rs index 676689f2ad7..862f2b70b33 100644 --- a/rand_distr/src/weighted_alias.rs +++ b/rand_distr/src/weighted/weighted_alias.rs @@ -9,7 +9,7 @@ //! This module contains an implementation of alias method for sampling random //! indices with probabilities proportional to a collection of weights. -use super::WeightError; +use super::Error; use crate::{uniform::SampleUniform, Distribution, Uniform}; use alloc::{boxed::Box, vec, vec::Vec}; use core::fmt; @@ -41,7 +41,7 @@ use serde::{Deserialize, Serialize}; /// # Example /// /// ``` -/// use rand_distr::WeightedAliasIndex; +/// use rand_distr::weighted::WeightedAliasIndex; /// use rand::prelude::*; /// /// let choices = vec!['a', 'b', 'c']; @@ -85,14 +85,14 @@ impl WeightedAliasIndex { /// Creates a new [`WeightedAliasIndex`]. /// /// Error cases: - /// - [`WeightError::InvalidInput`] when `weights.len()` is zero or greater than `u32::MAX`. - /// - [`WeightError::InvalidWeight`] when a weight is not-a-number, + /// - [`Error::InvalidInput`] when `weights.len()` is zero or greater than `u32::MAX`. + /// - [`Error::InvalidWeight`] when a weight is not-a-number, /// negative or greater than `max = W::MAX / weights.len()`. - /// - [`WeightError::InsufficientNonZero`] when the sum of all weights is zero. - pub fn new(weights: Vec) -> Result { + /// - [`Error::InsufficientNonZero`] when the sum of all weights is zero. + pub fn new(weights: Vec) -> Result { let n = weights.len(); if n == 0 || n > u32::MAX as usize { - return Err(WeightError::InvalidInput); + return Err(Error::InvalidInput); } let n = n as u32; @@ -103,7 +103,7 @@ impl WeightedAliasIndex { .iter() .all(|&w| W::ZERO <= w && w <= max_weight_size) { - return Err(WeightError::InvalidWeight); + return Err(Error::InvalidWeight); } // The sum of weights will represent 100% of no alias odds. @@ -115,7 +115,7 @@ impl WeightedAliasIndex { weight_sum }; if weight_sum == W::ZERO { - return Err(WeightError::InsufficientNonZero); + return Err(Error::InsufficientNonZero); } // `weight_sum` would have been zero if `try_from_lossy` causes an error here. @@ -384,23 +384,23 @@ mod test { // Floating point special cases assert_eq!( WeightedAliasIndex::new(vec![f32::INFINITY]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![-0_f32]).unwrap_err(), - WeightError::InsufficientNonZero + Error::InsufficientNonZero ); assert_eq!( WeightedAliasIndex::new(vec![-1_f32]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![f32::NEG_INFINITY]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![f32::NAN]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); } @@ -418,11 +418,11 @@ mod test { // Signed integer special cases assert_eq!( WeightedAliasIndex::new(vec![-1_i128]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![i128::MIN]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); } @@ -440,11 +440,11 @@ mod test { // Signed integer special cases assert_eq!( WeightedAliasIndex::new(vec![-1_i8]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); assert_eq!( WeightedAliasIndex::new(vec![i8::MIN]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); } @@ -491,15 +491,15 @@ mod test { assert_eq!( WeightedAliasIndex::::new(vec![]).unwrap_err(), - WeightError::InvalidInput + Error::InvalidInput ); assert_eq!( WeightedAliasIndex::new(vec![W::ZERO]).unwrap_err(), - WeightError::InsufficientNonZero + Error::InsufficientNonZero ); assert_eq!( WeightedAliasIndex::new(vec![W::MAX, W::MAX]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); } diff --git a/rand_distr/src/weighted_tree.rs b/rand_distr/src/weighted/weighted_tree.rs similarity index 87% rename from rand_distr/src/weighted_tree.rs rename to rand_distr/src/weighted/weighted_tree.rs index 355373a1b5a..dd315aa5f8f 100644 --- a/rand_distr/src/weighted_tree.rs +++ b/rand_distr/src/weighted/weighted_tree.rs @@ -11,11 +11,10 @@ use core::ops::SubAssign; -use super::WeightError; +use super::{Error, Weight}; use crate::Distribution; use alloc::vec::Vec; use rand::distr::uniform::{SampleBorrow, SampleUniform}; -use rand::distr::Weight; use rand::Rng; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -30,7 +29,7 @@ use serde::{Deserialize, Serialize}; /// /// # Key differences /// -/// The main distinction between [`WeightedTreeIndex`] and [`rand::distr::WeightedIndex`] +/// The main distinction between [`WeightedTreeIndex`] and [`WeightedIndex`] /// lies in the internal representation of weights. In [`WeightedTreeIndex`], /// weights are structured as a tree, which is optimized for frequent updates of the weights. /// @@ -58,7 +57,7 @@ use serde::{Deserialize, Serialize}; /// # Example /// /// ``` -/// use rand_distr::WeightedTreeIndex; +/// use rand_distr::weighted::WeightedTreeIndex; /// use rand::prelude::*; /// /// let choices = vec!['a', 'b', 'c']; @@ -77,6 +76,7 @@ use serde::{Deserialize, Serialize}; /// ``` /// /// [`WeightedTreeIndex`]: WeightedTreeIndex +/// [`WeightedIndex`]: super::WeightedIndex #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serde", @@ -99,9 +99,9 @@ impl + Weight> /// Creates a new [`WeightedTreeIndex`] from a slice of weights. /// /// Error cases: - /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. - /// - [`WeightError::Overflow`] when the sum of all weights overflows. - pub fn new(weights: I) -> Result + /// - [`Error::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`Error::Overflow`] when the sum of all weights overflows. + pub fn new(weights: I) -> Result where I: IntoIterator, I::Item: SampleBorrow, @@ -109,7 +109,7 @@ impl + Weight> let mut subtotals: Vec = weights.into_iter().map(|x| x.borrow().clone()).collect(); for weight in subtotals.iter() { if !(*weight >= W::ZERO) { - return Err(WeightError::InvalidWeight); + return Err(Error::InvalidWeight); } } let n = subtotals.len(); @@ -118,7 +118,7 @@ impl + Weight> let parent = (i - 1) / 2; subtotals[parent] .checked_add_assign(&w) - .map_err(|()| WeightError::Overflow)?; + .map_err(|()| Error::Overflow)?; } Ok(Self { subtotals }) } @@ -169,16 +169,16 @@ impl + Weight> /// Appends a new weight at the end. /// /// Error cases: - /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. - /// - [`WeightError::Overflow`] when the sum of all weights overflows. - pub fn push(&mut self, weight: W) -> Result<(), WeightError> { + /// - [`Error::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`Error::Overflow`] when the sum of all weights overflows. + pub fn push(&mut self, weight: W) -> Result<(), Error> { if !(weight >= W::ZERO) { - return Err(WeightError::InvalidWeight); + return Err(Error::InvalidWeight); } if let Some(total) = self.subtotals.first() { let mut total = total.clone(); if total.checked_add_assign(&weight).is_err() { - return Err(WeightError::Overflow); + return Err(Error::Overflow); } } let mut index = self.len(); @@ -193,11 +193,11 @@ impl + Weight> /// Updates the weight at an index. /// /// Error cases: - /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. - /// - [`WeightError::Overflow`] when the sum of all weights overflows. - pub fn update(&mut self, mut index: usize, weight: W) -> Result<(), WeightError> { + /// - [`Error::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`Error::Overflow`] when the sum of all weights overflows. + pub fn update(&mut self, mut index: usize, weight: W) -> Result<(), Error> { if !(weight >= W::ZERO) { - return Err(WeightError::InvalidWeight); + return Err(Error::InvalidWeight); } let old_weight = self.get(index); if weight > old_weight { @@ -206,7 +206,7 @@ impl + Weight> if let Some(total) = self.subtotals.first() { let mut total = total.clone(); if total.checked_add_assign(&difference).is_err() { - return Err(WeightError::Overflow); + return Err(Error::Overflow); } } self.subtotals[index] @@ -246,10 +246,10 @@ impl + Weight> /// /// Returns an error if there are no elements or all weights are zero. This /// is unlike [`Distribution::sample`], which panics in those cases. - pub fn try_sample(&self, rng: &mut R) -> Result { + pub fn try_sample(&self, rng: &mut R) -> Result { let total_weight = self.subtotals.first().cloned().unwrap_or(W::ZERO); if total_weight == W::ZERO { - return Err(WeightError::InsufficientNonZero); + return Err(Error::InsufficientNonZero); } let mut target_weight = rng.random_range(W::ZERO..total_weight); let mut index = 0; @@ -306,19 +306,16 @@ mod test { let tree = WeightedTreeIndex::::new(&[]).unwrap(); assert_eq!( tree.try_sample(&mut rng).unwrap_err(), - WeightError::InsufficientNonZero + Error::InsufficientNonZero ); } #[test] fn test_overflow_error() { - assert_eq!( - WeightedTreeIndex::new([i32::MAX, 2]), - Err(WeightError::Overflow) - ); + assert_eq!(WeightedTreeIndex::new([i32::MAX, 2]), Err(Error::Overflow)); let mut tree = WeightedTreeIndex::new([i32::MAX - 2, 1]).unwrap(); - assert_eq!(tree.push(3), Err(WeightError::Overflow)); - assert_eq!(tree.update(1, 4), Err(WeightError::Overflow)); + assert_eq!(tree.push(3), Err(Error::Overflow)); + assert_eq!(tree.update(1, 4), Err(Error::Overflow)); tree.update(1, 2).unwrap(); } @@ -328,7 +325,7 @@ mod test { let mut rng = crate::test::rng(0x9c9fa0b0580a7031); assert_eq!( tree.try_sample(&mut rng).unwrap_err(), - WeightError::InsufficientNonZero + Error::InsufficientNonZero ); } @@ -336,13 +333,13 @@ mod test { fn test_invalid_weight_error() { assert_eq!( WeightedTreeIndex::::new([1, -1]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); #[allow(clippy::needless_borrows_for_generic_args)] let mut tree = WeightedTreeIndex::::new(&[]).unwrap(); - assert_eq!(tree.push(-1).unwrap_err(), WeightError::InvalidWeight); + assert_eq!(tree.push(-1).unwrap_err(), Error::InvalidWeight); tree.push(1).unwrap(); - assert_eq!(tree.update(0, -1).unwrap_err(), WeightError::InvalidWeight); + assert_eq!(tree.update(0, -1).unwrap_err(), Error::InvalidWeight); } #[test] diff --git a/src/distr/distribution.rs b/src/distr/distribution.rs index f9385ec6172..6f4e202647e 100644 --- a/src/distr/distribution.rs +++ b/src/distr/distribution.rs @@ -69,40 +69,37 @@ pub trait Distribution { /// println!("Not a 6; rolling again!"); /// } /// ``` - fn sample_iter(self, rng: R) -> DistIter + fn sample_iter(self, rng: R) -> Iter where R: Rng, Self: Sized, { - DistIter { + Iter { distr: self, rng, phantom: core::marker::PhantomData, } } - /// Create a distribution of values of 'S' by mapping the output of `Self` - /// through the closure `F` + /// Map sampled values to type `S` /// /// # Example /// /// ``` /// use rand::distr::{Distribution, Uniform}; /// - /// let mut rng = rand::rng(); - /// /// let die = Uniform::new_inclusive(1, 6).unwrap(); /// let even_number = die.map(|num| num % 2 == 0); - /// while !even_number.sample(&mut rng) { + /// while !even_number.sample(&mut rand::rng()) { /// println!("Still odd; rolling again!"); /// } /// ``` - fn map(self, func: F) -> DistMap + fn map(self, func: F) -> Map where F: Fn(T) -> S, Self: Sized, { - DistMap { + Map { distr: self, func, phantom: core::marker::PhantomData, @@ -116,21 +113,22 @@ impl + ?Sized> Distribution for &D { } } -/// An iterator that generates random values of `T` with distribution `D`, -/// using `R` as the source of randomness. +/// An iterator over a [`Distribution`] /// -/// This `struct` is created by the [`sample_iter`] method on [`Distribution`]. -/// See its documentation for more. +/// This iterator yields random values of type `T` with distribution `D` +/// from a random generator of type `R`. /// -/// [`sample_iter`]: Distribution::sample_iter +/// Construct this `struct` using [`Distribution::sample_iter`] or +/// [`Rng::sample_iter`]. It is also used by [`Rng::random_iter`] and +/// [`crate::random_iter`]. #[derive(Debug)] -pub struct DistIter { +pub struct Iter { distr: D, rng: R, phantom: core::marker::PhantomData, } -impl Iterator for DistIter +impl Iterator for Iter where D: Distribution, R: Rng, @@ -150,26 +148,25 @@ where } } -impl iter::FusedIterator for DistIter +impl iter::FusedIterator for Iter where D: Distribution, R: Rng, { } -/// A distribution of values of type `S` derived from the distribution `D` -/// by mapping its output of type `T` through the closure `F`. +/// A [`Distribution`] which maps sampled values to type `S` /// /// This `struct` is created by the [`Distribution::map`] method. /// See its documentation for more. #[derive(Debug)] -pub struct DistMap { +pub struct Map { distr: D, func: F, phantom: core::marker::PhantomData S>, } -impl Distribution for DistMap +impl Distribution for Map where D: Distribution, F: Fn(T) -> S, @@ -179,16 +176,23 @@ where } } -/// `String` sampler +/// Sample or extend a [`String`] /// -/// Sampling a `String` of random characters is not quite the same as collecting -/// a sequence of chars. This trait contains some helpers. +/// Helper methods to extend a [`String`] or sample a new [`String`]. #[cfg(feature = "alloc")] -pub trait DistString { +pub trait SampleString { /// Append `len` random chars to `string` + /// + /// Note: implementations may leave `string` with excess capacity. If this + /// is undesirable, consider calling [`String::shrink_to_fit`] after this + /// method. fn append_string(&self, rng: &mut R, string: &mut String, len: usize); - /// Generate a `String` of `len` random chars + /// Generate a [`String`] of `len` random chars + /// + /// Note: implementations may leave the string with excess capacity. If this + /// is undesirable, consider calling [`String::shrink_to_fit`] after this + /// method. #[inline] fn sample_string(&self, rng: &mut R, len: usize) -> String { let mut s = String::new(); @@ -246,7 +250,7 @@ mod tests { #[test] #[cfg(feature = "alloc")] fn test_dist_string() { - use crate::distr::{Alphanumeric, DistString, StandardUniform}; + use crate::distr::{Alphanumeric, SampleString, StandardUniform}; use core::str; let mut rng = crate::test::rng(213); diff --git a/src/distr/mod.rs b/src/distr/mod.rs index 84bf4925a27..10016119ba2 100644 --- a/src/distr/mod.rs +++ b/src/distr/mod.rs @@ -69,8 +69,7 @@ //! Sampling a simple true/false outcome with a given probability has a name: //! the [`Bernoulli`] distribution (this is used by [`Rng::random_bool`]). //! -//! For weighted sampling from a sequence of discrete values, use the -//! [`WeightedIndex`] distribution. +//! For weighted sampling of discrete values see the [`weighted`] module. //! //! This crate no longer includes other non-uniform distributions; instead //! it is recommended that you use either [`rand_distr`] or [`statrs`]. @@ -89,28 +88,25 @@ mod distribution; mod float; mod integer; mod other; -mod slice; mod utils; -#[cfg(feature = "alloc")] -mod weighted_index; #[doc(hidden)] pub mod hidden_export { pub use super::float::IntoFloat; // used by rand_distr } +pub mod slice; pub mod uniform; +#[cfg(feature = "alloc")] +pub mod weighted; pub use self::bernoulli::{Bernoulli, BernoulliError}; #[cfg(feature = "alloc")] -pub use self::distribution::DistString; -pub use self::distribution::{DistIter, DistMap, Distribution}; +pub use self::distribution::SampleString; +pub use self::distribution::{Distribution, Iter, Map}; pub use self::float::{Open01, OpenClosed01}; pub use self::other::Alphanumeric; -pub use self::slice::Slice; #[doc(inline)] pub use self::uniform::Uniform; -#[cfg(feature = "alloc")] -pub use self::weighted_index::{Weight, WeightError, WeightedIndex}; #[allow(unused)] use crate::Rng; diff --git a/src/distr/other.rs b/src/distr/other.rs index 8e957f07446..9890bdafe6d 100644 --- a/src/distr/other.rs +++ b/src/distr/other.rs @@ -14,7 +14,7 @@ use core::char; use core::num::Wrapping; #[cfg(feature = "alloc")] -use crate::distr::DistString; +use crate::distr::SampleString; use crate::distr::{Distribution, StandardUniform, Uniform}; use crate::Rng; @@ -42,10 +42,10 @@ use serde::{Deserialize, Serialize}; /// println!("Random chars: {}", chars); /// ``` /// -/// The [`DistString`] trait provides an easier method of generating -/// a random `String`, and offers more efficient allocation: +/// The [`SampleString`] trait provides an easier method of generating +/// a random [`String`], and offers more efficient allocation: /// ``` -/// use rand::distr::{Alphanumeric, DistString}; +/// use rand::distr::{Alphanumeric, SampleString}; /// let string = Alphanumeric.sample_string(&mut rand::rng(), 16); /// println!("Random string: {}", string); /// ``` @@ -93,10 +93,8 @@ impl Distribution for StandardUniform { } } -/// Note: the `String` is potentially left with excess capacity; optionally the -/// user may call `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -impl DistString for StandardUniform { +impl SampleString for StandardUniform { fn append_string(&self, rng: &mut R, s: &mut String, len: usize) { // A char is encoded with at most four bytes, thus this reservation is // guaranteed to be sufficient. We do not shrink_to_fit afterwards so @@ -126,7 +124,7 @@ impl Distribution for Alphanumeric { } #[cfg(feature = "alloc")] -impl DistString for Alphanumeric { +impl SampleString for Alphanumeric { fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { unsafe { let v = string.as_mut_vec(); diff --git a/src/distr/slice.rs b/src/distr/slice.rs index 3eee65a92ce..07e243fec5d 100644 --- a/src/distr/slice.rs +++ b/src/distr/slice.rs @@ -6,6 +6,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! Distributions over slices + use core::num::NonZeroUsize; use crate::distr::uniform::{UniformSampler, UniformUsize}; @@ -13,36 +15,26 @@ use crate::distr::Distribution; #[cfg(feature = "alloc")] use alloc::string::String; -/// A distribution to sample items uniformly from a slice. -/// -/// [`Slice::new`] constructs a distribution referencing a slice and uniformly -/// samples references from the items in the slice. It may do extra work up -/// front to make sampling of multiple values faster; if only one sample from -/// the slice is required, [`IndexedRandom::choose`] can be more efficient. -/// -/// Steps are taken to avoid bias which might be present in naive -/// implementations; for example `slice[rng.gen() % slice.len()]` samples from -/// the slice, but may be more likely to select numbers in the low range than -/// other values. +/// A distribution to uniformly sample elements of a slice /// -/// This distribution samples with replacement; each sample is independent. -/// Sampling without replacement requires state to be retained, and therefore -/// cannot be handled by a distribution; you should instead consider methods -/// on [`IndexedRandom`], such as [`IndexedRandom::choose_multiple`]. +/// Like [`IndexedRandom::choose`], this uniformly samples elements of a slice +/// without modification of the slice (so called "sampling with replacement"). +/// This distribution object may be a little faster for repeated sampling (but +/// slower for small numbers of samples). /// -/// # Example +/// ## Examples /// +/// Since this is a distribution, [`Rng::sample_iter`] and +/// [`Distribution::sample_iter`] may be used, for example: /// ``` -/// use rand::Rng; -/// use rand::distr::Slice; +/// use rand::distr::{Distribution, slice::Choose}; /// /// let vowels = ['a', 'e', 'i', 'o', 'u']; -/// let vowels_dist = Slice::new(&vowels).unwrap(); -/// let rng = rand::rng(); +/// let vowels_dist = Choose::new(&vowels).unwrap(); /// /// // build a string of 10 vowels -/// let vowel_string: String = rng -/// .sample_iter(&vowels_dist) +/// let vowel_string: String = vowels_dist +/// .sample_iter(&mut rand::rng()) /// .take(10) /// .collect(); /// @@ -51,33 +43,31 @@ use alloc::string::String; /// assert!(vowel_string.chars().all(|c| vowels.contains(&c))); /// ``` /// -/// For a single sample, [`IndexedRandom::choose`][crate::seq::IndexedRandom::choose] -/// may be preferred: -/// +/// For a single sample, [`IndexedRandom::choose`] may be preferred: /// ``` /// use rand::seq::IndexedRandom; /// /// let vowels = ['a', 'e', 'i', 'o', 'u']; /// let mut rng = rand::rng(); /// -/// println!("{}", vowels.choose(&mut rng).unwrap()) +/// println!("{}", vowels.choose(&mut rng).unwrap()); /// ``` /// -/// [`IndexedRandom`]: crate::seq::IndexedRandom /// [`IndexedRandom::choose`]: crate::seq::IndexedRandom::choose -/// [`IndexedRandom::choose_multiple`]: crate::seq::IndexedRandom::choose_multiple +/// [`Rng::sample_iter`]: crate::Rng::sample_iter #[derive(Debug, Clone, Copy)] -pub struct Slice<'a, T> { +pub struct Choose<'a, T> { slice: &'a [T], range: UniformUsize, num_choices: NonZeroUsize, } -impl<'a, T> Slice<'a, T> { - /// Create a new `Slice` instance which samples uniformly from the slice. - /// Returns `Err` if the slice is empty. - pub fn new(slice: &'a [T]) -> Result { - let num_choices = NonZeroUsize::new(slice.len()).ok_or(EmptySlice)?; +impl<'a, T> Choose<'a, T> { + /// Create a new `Choose` instance which samples uniformly from the slice. + /// + /// Returns error [`Empty`] if the slice is empty. + pub fn new(slice: &'a [T]) -> Result { + let num_choices = NonZeroUsize::new(slice.len()).ok_or(Empty)?; Ok(Self { slice, @@ -92,7 +82,7 @@ impl<'a, T> Slice<'a, T> { } } -impl<'a, T> Distribution<&'a T> for Slice<'a, T> { +impl<'a, T> Distribution<&'a T> for Choose<'a, T> { fn sample(&self, rng: &mut R) -> &'a T { let idx = self.range.sample(rng); @@ -110,24 +100,26 @@ impl<'a, T> Distribution<&'a T> for Slice<'a, T> { } } -/// Error type indicating that a [`Slice`] distribution was improperly -/// constructed with an empty slice. +/// Error: empty slice +/// +/// This error is returned when [`Choose::new`] is given an empty slice. #[derive(Debug, Clone, Copy)] -pub struct EmptySlice; +pub struct Empty; -impl core::fmt::Display for EmptySlice { +impl core::fmt::Display for Empty { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "Tried to create a `distr::Slice` with an empty slice") + write!( + f, + "Tried to create a `rand::distr::slice::Choose` with an empty slice" + ) } } #[cfg(feature = "std")] -impl std::error::Error for EmptySlice {} +impl std::error::Error for Empty {} -/// Note: the `String` is potentially left with excess capacity; optionally the -/// user may call `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -impl super::DistString for Slice<'_, char> { +impl super::SampleString for Choose<'_, char> { fn append_string(&self, rng: &mut R, string: &mut String, len: usize) { // Get the max char length to minimize extra space. // Limit this check to avoid searching for long slice. @@ -168,7 +160,7 @@ mod test { #[test] fn value_stability() { let rng = crate::test::rng(651); - let slice = Slice::new(b"escaped emus explore extensively").unwrap(); + let slice = Choose::new(b"escaped emus explore extensively").unwrap(); let expected = b"eaxee"; assert!(iter::zip(slice.sample_iter(rng), expected).all(|(a, b)| a == b)); } diff --git a/src/distr/uniform_other.rs b/src/distr/uniform_other.rs index 42a7ff78134..03533debcd8 100644 --- a/src/distr/uniform_other.rs +++ b/src/distr/uniform_other.rs @@ -90,11 +90,8 @@ impl UniformSampler for UniformChar { } } -/// Note: the `String` is potentially left with excess capacity if the range -/// includes non ascii chars; optionally the user may call -/// `string.shrink_to_fit()` afterwards. #[cfg(feature = "alloc")] -impl crate::distr::DistString for Uniform { +impl crate::distr::SampleString for Uniform { fn append_string( &self, rng: &mut R, @@ -281,7 +278,7 @@ mod tests { } #[cfg(feature = "alloc")] { - use crate::distr::DistString; + use crate::distr::SampleString; let string1 = d.sample_string(&mut rng, 100); assert_eq!(string1.capacity(), 300); let string2 = Uniform::new( diff --git a/src/distr/weighted/mod.rs b/src/distr/weighted/mod.rs new file mode 100644 index 00000000000..368c5b0703d --- /dev/null +++ b/src/distr/weighted/mod.rs @@ -0,0 +1,115 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Weighted (index) sampling +//! +//! Primarily, this module houses the [`WeightedIndex`] distribution. +//! See also [`rand_distr::weighted`] for alternative implementations supporting +//! potentially-faster sampling or a more easily modifiable tree structure. +//! +//! [`rand_distr::weighted`]: https://docs.rs/rand_distr/latest/rand_distr/weighted/index.html + +use core::fmt; +mod weighted_index; + +pub use weighted_index::WeightedIndex; + +/// Bounds on a weight +/// +/// See usage in [`WeightedIndex`]. +pub trait Weight: Clone { + /// Representation of 0 + const ZERO: Self; + + /// Checked addition + /// + /// - `Result::Ok`: On success, `v` is added to `self` + /// - `Result::Err`: Returns an error when `Self` cannot represent the + /// result of `self + v` (i.e. overflow). The value of `self` should be + /// discarded. + #[allow(clippy::result_unit_err)] + fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()>; +} + +macro_rules! impl_weight_int { + ($t:ty) => { + impl Weight for $t { + const ZERO: Self = 0; + fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()> { + match self.checked_add(*v) { + Some(sum) => { + *self = sum; + Ok(()) + } + None => Err(()), + } + } + } + }; + ($t:ty, $($tt:ty),*) => { + impl_weight_int!($t); + impl_weight_int!($($tt),*); + } +} +impl_weight_int!(i8, i16, i32, i64, i128, isize); +impl_weight_int!(u8, u16, u32, u64, u128, usize); + +macro_rules! impl_weight_float { + ($t:ty) => { + impl Weight for $t { + const ZERO: Self = 0.0; + + fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()> { + // Floats have an explicit representation for overflow + *self += *v; + Ok(()) + } + } + }; +} +impl_weight_float!(f32); +impl_weight_float!(f64); + +/// Invalid weight errors +/// +/// This type represents errors from [`WeightedIndex::new`], +/// [`WeightedIndex::update_weights`] and other weighted distributions. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +// Marked non_exhaustive to allow a new error code in the solution to #1476. +#[non_exhaustive] +pub enum Error { + /// The input weight sequence is empty, too long, or wrongly ordered + InvalidInput, + + /// A weight is negative, too large for the distribution, or not a valid number + InvalidWeight, + + /// Not enough non-zero weights are available to sample values + /// + /// When attempting to sample a single value this implies that all weights + /// are zero. When attempting to sample `amount` values this implies that + /// less than `amount` weights are greater than zero. + InsufficientNonZero, + + /// Overflow when calculating the sum of weights + Overflow, +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + Error::InvalidInput => "Weights sequence is empty/too long/unordered", + Error::InvalidWeight => "A weight is negative, too large or not a valid number", + Error::InsufficientNonZero => "Not enough weights > zero", + Error::Overflow => "Overflow when summing weights", + }) + } +} diff --git a/src/distr/weighted_index.rs b/src/distr/weighted/weighted_index.rs similarity index 77% rename from src/distr/weighted_index.rs rename to src/distr/weighted/weighted_index.rs index fef5728e41a..4bb9d141fb3 100644 --- a/src/distr/weighted_index.rs +++ b/src/distr/weighted/weighted_index.rs @@ -6,16 +6,14 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Weighted index sampling - +use super::{Error, Weight}; use crate::distr::uniform::{SampleBorrow, SampleUniform, UniformSampler}; use crate::distr::Distribution; use crate::Rng; -use core::fmt; // Note that this whole module is only imported if feature="alloc" is enabled. use alloc::vec::Vec; -use core::fmt::Debug; +use core::fmt::{self, Debug}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -33,12 +31,9 @@ use serde::{Deserialize, Serialize}; /// # Performance /// /// Time complexity of sampling from `WeightedIndex` is `O(log N)` where -/// `N` is the number of weights. There are two alternative implementations with -/// different runtimes characteristics: -/// * [`rand_distr::weighted_alias`] supports `O(1)` sampling, but with much higher -/// initialisation cost. -/// * [`rand_distr::weighted_tree`] keeps the weights in a tree structure where sampling -/// and updating is `O(log N)`. +/// `N` is the number of weights. +/// See also [`rand_distr::weighted`] for alternative implementations supporting +/// potentially-faster sampling or a more easily modifiable tree structure. /// /// A `WeightedIndex` contains a `Vec` and a [`Uniform`] and so its /// size is the sum of the size of those objects, possibly plus some alignment. @@ -59,7 +54,7 @@ use serde::{Deserialize, Serialize}; /// /// ``` /// use rand::prelude::*; -/// use rand::distr::WeightedIndex; +/// use rand::distr::weighted::WeightedIndex; /// /// let choices = ['a', 'b', 'c']; /// let weights = [2, 1, 1]; @@ -80,8 +75,7 @@ use serde::{Deserialize, Serialize}; /// /// [`Uniform`]: crate::distr::Uniform /// [`RngCore`]: crate::RngCore -/// [`rand_distr::weighted_alias`]: https://docs.rs/rand_distr/*/rand_distr/weighted_alias/index.html -/// [`rand_distr::weighted_tree`]: https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html +/// [`rand_distr::weighted`]: https://docs.rs/rand_distr/latest/rand_distr/weighted/index.html #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct WeightedIndex { @@ -96,28 +90,24 @@ impl WeightedIndex { /// implementation of [`Uniform`] exists. /// /// Error cases: - /// - [`WeightError::InvalidInput`] when the iterator `weights` is empty. - /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. - /// - [`WeightError::InsufficientNonZero`] when the sum of all weights is zero. - /// - [`WeightError::Overflow`] when the sum of all weights overflows. + /// - [`Error::InvalidInput`] when the iterator `weights` is empty. + /// - [`Error::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`Error::InsufficientNonZero`] when the sum of all weights is zero. + /// - [`Error::Overflow`] when the sum of all weights overflows. /// /// [`Uniform`]: crate::distr::uniform::Uniform - pub fn new(weights: I) -> Result, WeightError> + pub fn new(weights: I) -> Result, Error> where I: IntoIterator, I::Item: SampleBorrow, X: Weight, { let mut iter = weights.into_iter(); - let mut total_weight: X = iter - .next() - .ok_or(WeightError::InvalidInput)? - .borrow() - .clone(); + let mut total_weight: X = iter.next().ok_or(Error::InvalidInput)?.borrow().clone(); let zero = X::ZERO; if !(total_weight >= zero) { - return Err(WeightError::InvalidWeight); + return Err(Error::InvalidWeight); } let mut weights = Vec::::with_capacity(iter.size_hint().0); @@ -125,17 +115,17 @@ impl WeightedIndex { // Note that `!(w >= x)` is not equivalent to `w < x` for partially // ordered types due to NaNs which are equal to nothing. if !(w.borrow() >= &zero) { - return Err(WeightError::InvalidWeight); + return Err(Error::InvalidWeight); } weights.push(total_weight.clone()); if let Err(()) = total_weight.checked_add_assign(w.borrow()) { - return Err(WeightError::Overflow); + return Err(Error::Overflow); } } if total_weight == zero { - return Err(WeightError::InsufficientNonZero); + return Err(Error::InsufficientNonZero); } let distr = X::Sampler::new(zero, total_weight.clone()).unwrap(); @@ -155,10 +145,10 @@ impl WeightedIndex { /// allocation internally. /// /// In case of error, `self` is not modified. Error cases: - /// - [`WeightError::InvalidInput`] when `new_weights` are not ordered by + /// - [`Error::InvalidInput`] when `new_weights` are not ordered by /// index or an index is too large. - /// - [`WeightError::InvalidWeight`] when a weight is not-a-number or negative. - /// - [`WeightError::InsufficientNonZero`] when the sum of all weights is zero. + /// - [`Error::InvalidWeight`] when a weight is not-a-number or negative. + /// - [`Error::InsufficientNonZero`] when the sum of all weights is zero. /// Note that due to floating-point loss of precision, this case is not /// always correctly detected; usage of a fixed-point weight type may be /// preferred. @@ -166,7 +156,7 @@ impl WeightedIndex { /// Updates take `O(N)` time. If you need to frequently update weights, consider /// [`rand_distr::weighted_tree`](https://docs.rs/rand_distr/*/rand_distr/weighted_tree/index.html) /// as an alternative where an update is `O(log N)`. - pub fn update_weights(&mut self, new_weights: &[(usize, &X)]) -> Result<(), WeightError> + pub fn update_weights(&mut self, new_weights: &[(usize, &X)]) -> Result<(), Error> where X: for<'a> core::ops::AddAssign<&'a X> + for<'a> core::ops::SubAssign<&'a X> @@ -187,14 +177,14 @@ impl WeightedIndex { for &(i, w) in new_weights { if let Some(old_i) = prev_i { if old_i >= i { - return Err(WeightError::InvalidInput); + return Err(Error::InvalidInput); } } if !(*w >= zero) { - return Err(WeightError::InvalidWeight); + return Err(Error::InvalidWeight); } if i > self.cumulative_weights.len() { - return Err(WeightError::InvalidInput); + return Err(Error::InvalidInput); } let mut old_w = if i < self.cumulative_weights.len() { @@ -211,7 +201,7 @@ impl WeightedIndex { prev_i = Some(i); } if total_weight <= zero { - return Err(WeightError::InsufficientNonZero); + return Err(Error::InsufficientNonZero); } // Update the weights. Because we checked all the preconditions in the @@ -306,7 +296,7 @@ impl WeightedIndex { /// # Example /// /// ``` - /// use rand::distr::WeightedIndex; + /// use rand::distr::weighted::WeightedIndex; /// /// let weights = [0, 1, 2]; /// let dist = WeightedIndex::new(&weights).unwrap(); @@ -341,7 +331,7 @@ impl WeightedIndex { /// # Example /// /// ``` - /// use rand::distr::WeightedIndex; + /// use rand::distr::weighted::WeightedIndex; /// /// let weights = [1, 2, 3]; /// let mut dist = WeightedIndex::new(&weights).unwrap(); @@ -377,62 +367,6 @@ where } } -/// Bounds on a weight -/// -/// See usage in [`WeightedIndex`]. -pub trait Weight: Clone { - /// Representation of 0 - const ZERO: Self; - - /// Checked addition - /// - /// - `Result::Ok`: On success, `v` is added to `self` - /// - `Result::Err`: Returns an error when `Self` cannot represent the - /// result of `self + v` (i.e. overflow). The value of `self` should be - /// discarded. - #[allow(clippy::result_unit_err)] - fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()>; -} - -macro_rules! impl_weight_int { - ($t:ty) => { - impl Weight for $t { - const ZERO: Self = 0; - fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()> { - match self.checked_add(*v) { - Some(sum) => { - *self = sum; - Ok(()) - } - None => Err(()), - } - } - } - }; - ($t:ty, $($tt:ty),*) => { - impl_weight_int!($t); - impl_weight_int!($($tt),*); - } -} -impl_weight_int!(i8, i16, i32, i64, i128, isize); -impl_weight_int!(u8, u16, u32, u64, u128, usize); - -macro_rules! impl_weight_float { - ($t:ty) => { - impl Weight for $t { - const ZERO: Self = 0.0; - - fn checked_add_assign(&mut self, v: &Self) -> Result<(), ()> { - // Floats have an explicit representation for overflow - *self += *v; - Ok(()) - } - } - }; -} -impl_weight_float!(f32); -impl_weight_float!(f64); - #[cfg(test)] mod test { use super::*; @@ -457,15 +391,15 @@ mod test { fn test_accepting_nan() { assert_eq!( WeightedIndex::new([f32::NAN, 0.5]).unwrap_err(), - WeightError::InvalidWeight, + Error::InvalidWeight, ); assert_eq!( WeightedIndex::new([f32::NAN]).unwrap_err(), - WeightError::InvalidWeight, + Error::InvalidWeight, ); assert_eq!( WeightedIndex::new([0.5, f32::NAN]).unwrap_err(), - WeightError::InvalidWeight, + Error::InvalidWeight, ); assert_eq!( @@ -473,7 +407,7 @@ mod test { .unwrap() .update_weights(&[(0, &f32::NAN)]) .unwrap_err(), - WeightError::InvalidWeight, + Error::InvalidWeight, ) } @@ -533,24 +467,21 @@ mod test { assert_eq!( WeightedIndex::new(&[10][0..0]).unwrap_err(), - WeightError::InvalidInput + Error::InvalidInput ); assert_eq!( WeightedIndex::new([0]).unwrap_err(), - WeightError::InsufficientNonZero + Error::InsufficientNonZero ); assert_eq!( WeightedIndex::new([10, 20, -1, 30]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); assert_eq!( WeightedIndex::new([-10, 20, 1, 30]).unwrap_err(), - WeightError::InvalidWeight - ); - assert_eq!( - WeightedIndex::new([-10]).unwrap_err(), - WeightError::InvalidWeight + Error::InvalidWeight ); + assert_eq!(WeightedIndex::new([-10]).unwrap_err(), Error::InvalidWeight); } #[test] @@ -588,22 +519,22 @@ mod test { ( &[1i32, 0, 0][..], &[(0, &0)][..], - WeightError::InsufficientNonZero, + Error::InsufficientNonZero, ), ( &[10, 10, 10, 10][..], &[(1, &-11)][..], - WeightError::InvalidWeight, // A weight is negative + Error::InvalidWeight, // A weight is negative ), ( &[1, 2, 3, 4, 5][..], &[(1, &5), (0, &5)][..], // Wrong order - WeightError::InvalidInput, + Error::InvalidInput, ), ( &[1][..], &[(1, &1)][..], // Index too large - WeightError::InvalidInput, + Error::InvalidInput, ), ]; @@ -695,45 +626,6 @@ mod test { #[test] fn overflow() { - assert_eq!( - WeightedIndex::new([2, usize::MAX]), - Err(WeightError::Overflow) - ); - } -} - -/// Errors returned by [`WeightedIndex::new`], [`WeightedIndex::update_weights`] and other weighted distributions -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -// Marked non_exhaustive to allow a new error code in the solution to #1476. -#[non_exhaustive] -pub enum WeightError { - /// The input weight sequence is empty, too long, or wrongly ordered - InvalidInput, - - /// A weight is negative, too large for the distribution, or not a valid number - InvalidWeight, - - /// Not enough non-zero weights are available to sample values - /// - /// When attempting to sample a single value this implies that all weights - /// are zero. When attempting to sample `amount` values this implies that - /// less than `amount` weights are greater than zero. - InsufficientNonZero, - - /// Overflow when calculating the sum of weights - Overflow, -} - -#[cfg(feature = "std")] -impl std::error::Error for WeightError {} - -impl fmt::Display for WeightError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - WeightError::InvalidInput => "Weights sequence is empty/too long/unordered", - WeightError::InvalidWeight => "A weight is negative, too large or not a valid number", - WeightError::InsufficientNonZero => "Not enough weights > zero", - WeightError::Overflow => "Overflow when summing weights", - }) + assert_eq!(WeightedIndex::new([2, usize::MAX]), Err(Error::Overflow)); } } diff --git a/src/lib.rs b/src/lib.rs index b5bb4fcb2f2..54ae8840253 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -181,7 +181,7 @@ where /// ``` #[cfg(feature = "thread_rng")] #[inline] -pub fn random_iter() -> distr::DistIter +pub fn random_iter() -> distr::Iter where StandardUniform: Distribution, { diff --git a/src/rng.rs b/src/rng.rs index 04b71f74b79..258c87de273 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -117,7 +117,7 @@ pub trait Rng: RngCore { /// assert_eq!(&v, &[1, 2, 3, 4, 5]); /// ``` #[inline] - fn random_iter(self) -> distr::DistIter + fn random_iter(self) -> distr::Iter where Self: Sized, StandardUniform: Distribution, @@ -283,7 +283,7 @@ pub trait Rng: RngCore { /// println!("Not a 6; rolling again!"); /// } /// ``` - fn sample_iter(self, distr: D) -> distr::DistIter + fn sample_iter(self, distr: D) -> distr::Iter where D: Distribution, Self: Sized, diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 82601304da6..91d634d865e 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -19,7 +19,7 @@ //! //! Also see: //! -//! * [`crate::distr::WeightedIndex`] distribution which provides +//! * [`crate::distr::weighted::WeightedIndex`] distribution which provides //! weighted index sampling. //! //! In order to make results reproducible across 32-64 bit architectures, all @@ -37,7 +37,7 @@ mod index_; #[cfg(feature = "alloc")] #[doc(no_inline)] -pub use crate::distr::WeightError; +pub use crate::distr::weighted::Error as WeightError; pub use iterator::IteratorRandom; #[cfg(feature = "alloc")] pub use slice::SliceChooseIter; diff --git a/src/seq/slice.rs b/src/seq/slice.rs index 1fc10c09857..d48d9d2e9f3 100644 --- a/src/seq/slice.rs +++ b/src/seq/slice.rs @@ -13,7 +13,7 @@ use super::index; #[cfg(feature = "alloc")] use crate::distr::uniform::{SampleBorrow, SampleUniform}; #[cfg(feature = "alloc")] -use crate::distr::{Weight, WeightError}; +use crate::distr::weighted::{Error as WeightError, Weight}; use crate::Rng; use core::ops::{Index, IndexMut}; @@ -136,7 +136,7 @@ pub trait IndexedRandom: Index { /// /// For slices of length `n`, complexity is `O(n)`. /// For more information about the underlying algorithm, - /// see [`distr::WeightedIndex`]. + /// see the [`WeightedIndex`] distribution. /// /// See also [`choose_weighted_mut`]. /// @@ -153,7 +153,7 @@ pub trait IndexedRandom: Index { /// ``` /// [`choose`]: IndexedRandom::choose /// [`choose_weighted_mut`]: IndexedMutRandom::choose_weighted_mut - /// [`distr::WeightedIndex`]: crate::distr::WeightedIndex + /// [`WeightedIndex`]: crate::distr::weighted::WeightedIndex #[cfg(feature = "alloc")] fn choose_weighted( &self, @@ -166,7 +166,7 @@ pub trait IndexedRandom: Index { B: SampleBorrow, X: SampleUniform + Weight + PartialOrd, { - use crate::distr::{Distribution, WeightedIndex}; + use crate::distr::{weighted::WeightedIndex, Distribution}; let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; Ok(&self[distr.sample(rng)]) } @@ -273,13 +273,13 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { /// /// For slices of length `n`, complexity is `O(n)`. /// For more information about the underlying algorithm, - /// see [`distr::WeightedIndex`]. + /// see the [`WeightedIndex`] distribution. /// /// See also [`choose_weighted`]. /// /// [`choose_mut`]: IndexedMutRandom::choose_mut /// [`choose_weighted`]: IndexedRandom::choose_weighted - /// [`distr::WeightedIndex`]: crate::distr::WeightedIndex + /// [`WeightedIndex`]: crate::distr::weighted::WeightedIndex #[cfg(feature = "alloc")] fn choose_weighted_mut( &mut self, @@ -292,7 +292,7 @@ pub trait IndexedMutRandom: IndexedRandom + IndexMut { B: SampleBorrow, X: SampleUniform + Weight + PartialOrd, { - use crate::distr::{Distribution, WeightedIndex}; + use crate::distr::{weighted::WeightedIndex, Distribution}; let distr = WeightedIndex::new((0..self.len()).map(|idx| weight(&self[idx])))?; let index = distr.sample(rng); Ok(&mut self[index]) From 34da3214df7de717cb27b4e1527ed971f47de311 Mon Sep 17 00:00:00 2001 From: Haofei Liang Date: Tue, 14 Jan 2025 18:10:22 +0800 Subject: [PATCH 442/443] Enable `stdarch_x86_avx512` for cpu has `avx512bw` (#1551) --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 54ae8840253..e1a9ef4ddc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,10 @@ #![doc(test(attr(allow(unused_variables), deny(warnings))))] #![no_std] #![cfg_attr(feature = "simd_support", feature(portable_simd))] +#![cfg_attr( + all(feature = "simd_support", target_feature = "avx512bw"), + feature(stdarch_x86_avx512) +)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![allow( clippy::float_cmp, From 96f8df65ee6b4368d91a006f9c5b4a8050abae49 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 27 Jan 2025 13:37:36 +0000 Subject: [PATCH 443/443] Prepare 0.9.0 release (#1558) --- CHANGELOG.md | 103 +++++++++++++++++++++------------------ Cargo.toml | 8 +-- README.md | 21 +------- distr_test/Cargo.toml | 4 +- rand_chacha/CHANGELOG.md | 15 +++--- rand_chacha/Cargo.toml | 6 +-- rand_core/CHANGELOG.md | 18 +++---- rand_core/Cargo.toml | 4 +- rand_core/src/lib.rs | 19 ++++---- rand_distr/CHANGELOG.md | 54 ++++++++------------ rand_distr/Cargo.toml | 8 +-- rand_pcg/CHANGELOG.md | 15 +++--- rand_pcg/Cargo.toml | 6 +-- 13 files changed, 128 insertions(+), 153 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f2e62e9bc9..fded9d79aca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,98 +8,107 @@ A [separate changelog is kept for rand_core](rand_core/CHANGELOG.md). You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.html) useful. -## [0.9.0-beta.3] - 2025-01-03 -- Add feature `thread_rng` (#1547) -- Move `distr::Slice` -> `distr::slice::Choose`, `distr::EmptySlice` -> `distr::slice::Empty` (#1548) -- Rename trait `distr::DistString` -> `distr::SampleString` (#1548) -- Rename `distr::DistIter` -> `distr::Iter`, `distr::DistMap` -> `distr::Map` (#1548) -- Move `distr::{Weight, WeightError, WeightedIndex}` -> `distr::weighted::{Weight, Error, WeightedIndex}` (#1548) - -## [0.9.0-beta.1] - 2024-11-30 -- Bump `rand_core` version - -## [0.9.0-beta.0] - 2024-11-25 -This is a pre-release. To depend on this version, use `rand = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). - +## [0.9.0] - 2025-01-27 ### Security and unsafe - Policy: "rand is not a crypto library" (#1514) - Remove fork-protection from `ReseedingRng` and `ThreadRng`. Instead, it is recommended to call `ThreadRng::reseed` on fork. (#1379) - Use `zerocopy` to replace some `unsafe` code (#1349, #1393, #1446, #1502) -### Compilation options +### Dependencies - Bump the MSRV to 1.63.0 (#1207, #1246, #1269, #1341, #1416, #1536); note that 1.60.0 may work for dependents when using `--ignore-rust-version` +- Update to `rand_core` v0.9.0 (#1558) + +### Features - Support `std` feature without `getrandom` or `rand_chacha` (#1354) -- Improve `thread_rng` related docs (#1257) -- The `serde1` feature has been renamed `serde` (#1477) -- The implicit feature `rand_chacha` has been removed. This is enabled by `std_rng`. (#1473) - Enable feature `small_rng` by default (#1455) +- Remove implicit feature `rand_chacha`; use `std_rng` instead. (#1473) +- Rename feature `serde1` to `serde` (#1477) - Rename feature `getrandom` to `os_rng` (#1537) +- Add feature `thread_rng` (#1547) -### Inherited changes from `rand_core` +### API changes: rand_core traits - Add fn `RngCore::read_adapter` implementing `std::io::Read` (#1267) - Add trait `CryptoBlockRng: BlockRngCore`; make `trait CryptoRng: RngCore` (#1273) - Add traits `TryRngCore`, `TryCryptoRng` (#1424, #1499) +- Rename `fn SeedableRng::from_rng` -> `try_from_rng` and add infallible variant `fn from_rng` (#1424) +- Rename `fn SeedableRng::from_entropy` -> `from_os_rng` and add fallible variant `fn try_from_os_rng` (#1424) - Add bounds `Clone` and `AsRef` to associated type `SeedableRng::Seed` (#1491) -### Rng trait and top-level fns -- Rename fn `rand::thread_rng()` to `rand::rng()`, and remove from the prelude (#1506) -- Add top-level fns `random_iter`, `random_range`, `random_bool`, `random_ratio`, `fill` (#1488) +### API changes: Rng trait and top-level fns +- Rename fn `rand::thread_rng()` to `rand::rng()` and remove from the prelude (#1506) - Remove fn `rand::random()` from the prelude (#1506) +- Add top-level fns `random_iter`, `random_range`, `random_bool`, `random_ratio`, `fill` (#1488) - Re-introduce fn `Rng::gen_iter` as `random_iter` (#1305, #1500) - Rename fn `Rng::gen` to `random` to avoid conflict with the new `gen` keyword in Rust 2024 (#1438) - Rename fns `Rng::gen_range` to `random_range`, `gen_bool` to `random_bool`, `gen_ratio` to `random_ratio` (#1505) - Annotate panicking methods with `#[track_caller]` (#1442, #1447) -### RNGs -- Make `ReseedingRng::reseed` discard remaining data from the last block generated (#1379) -- Change fn `SmallRng::seed_from_u64` implementation (#1203) +### API changes: RNGs - Fix `::Seed` size to 256 bits (#1455) - Remove first parameter (`rng`) of `ReseedingRng::new` (#1533) -- Improve SmallRng initialization performance (#1482) -### Sequences -- Optimize fn `sample_floyd`, affecting output of `rand::seq::index::sample` and `rand::seq::SliceRandom::choose_multiple` (#1277) -- New, faster algorithms for `IteratorRandom::choose` and `choose_stable` (#1268) -- New, faster algorithms for `SliceRandom::shuffle` and `partial_shuffle` (#1272) +### API changes: Sequences - Split trait `SliceRandom` into `IndexedRandom`, `IndexedMutRandom`, `SliceRandom` (#1382) - Add `IndexedRandom::choose_multiple_array`, `index::sample_array` (#1453, #1469) -- Fix `IndexdRandom::choose_multiple_weighted` for very small seeds and optimize for large input length / low memory (#1530) -### Distributions +### API changes: Distributions: renames - Rename module `rand::distributions` to `rand::distr` (#1470) -- Relax `Sized` bound on `Distribution for &D` (#1278) - Rename distribution `Standard` to `StandardUniform` (#1526) +- Move `distr::Slice` -> `distr::slice::Choose`, `distr::EmptySlice` -> `distr::slice::Empty` (#1548) +- Rename trait `distr::DistString` -> `distr::SampleString` (#1548) +- Rename `distr::DistIter` -> `distr::Iter`, `distr::DistMap` -> `distr::Map` (#1548) + +### API changes: Distributions +- Relax `Sized` bound on `Distribution for &D` (#1278) - Remove impl of `Distribution>` for `StandardUniform` (#1526) - Let distribution `StandardUniform` support all `NonZero*` types (#1332) - Fns `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229) - Distribution `Uniform` implements `TryFrom` instead of `From` for ranges (#1229) -- Optimize distribution `Uniform`: use Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287) -- Add `UniformUsize` and use to make `Uniform` for `usize` portable (#1487) -- Remove support for generating `isize` and `usize` values with `Standard`, `Uniform` (except via `UniformUsize`) and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) -- Optimize fn `sample_single_inclusive` for floats (+~20% perf) (#1289) -- Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) +- Add `UniformUsize` (#1487) +- Remove support for generating `isize` and `usize` values with `StandardUniform`, `Uniform` (except via `UniformUsize`) and `Fill` and usage as a `WeightedAliasIndex` weight (#1487) - Add impl `DistString` for distributions `Slice` and `Uniform` (#1315) - Add fn `Slice::num_choices` (#1402) -- Fix portability of distribution `Slice` (#1469) -- Add trait `Weight`, allowing `WeightedIndex` to trap overflow (#1353) -- Add fns `weight, weights, total_weight` to distribution `WeightedIndex` (#1420) -- Rename enum `WeightedError` to `WeightError`, revising variants (#1382) and mark as `#[non_exhaustive]` (#1480) - Add fn `p()` for distribution `Bernoulli` to access probability (#1481) -### SIMD +### API changes: Weighted distributions +- Add `pub` module `rand::distr::weighted`, moving `WeightedIndex` there (#1548) +- Add trait `weighted::Weight`, allowing `WeightedIndex` to trap overflow (#1353) +- Add fns `weight, weights, total_weight` to distribution `WeightedIndex` (#1420) +- Rename enum `WeightedError` to `weighted::Error`, revising variants (#1382) and mark as `#[non_exhaustive]` (#1480) + +### API changes: SIMD - Switch to `std::simd`, expand SIMD & docs (#1239) -- Optimise SIMD widening multiply (#1247) -### Documentation -- Add `Cargo.lock.msrv` file (#1275) -- Docs: enable experimental `--generate-link-to-definition` feature (#1327) -- Better doc of crate features, use `doc_auto_cfg` (#1411, #1450) +### Reproducibility-breaking changes +- Make `ReseedingRng::reseed` discard remaining data from the last block generated (#1379) +- Change fn `SmallRng::seed_from_u64` implementation (#1203) +- Allow `UniformFloat::new` samples and `UniformFloat::sample_single` to yield `high` (#1462) +- Fix portability of distribution `Slice` (#1469) +- Make `Uniform` for `usize` portable via `UniformUsize` (#1487) +- Fix `IndexdRandom::choose_multiple_weighted` for very small seeds and optimize for large input length / low memory (#1530) + +### Reproducibility-breaking optimisations +- Optimize fn `sample_floyd`, affecting output of `rand::seq::index::sample` and `rand::seq::SliceRandom::choose_multiple` (#1277) +- New, faster algorithms for `IteratorRandom::choose` and `choose_stable` (#1268) +- New, faster algorithms for `SliceRandom::shuffle` and `partial_shuffle` (#1272) +- Optimize distribution `Uniform`: use Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287) +- Optimize fn `sample_single_inclusive` for floats (+~20% perf) (#1289) + +### Other optimisations +- Improve `SmallRng` initialization performance (#1482) +- Optimise SIMD widening multiply (#1247) ### Other +- Add `Cargo.lock.msrv` file (#1275) - Reformat with `rustfmt` and enforce (#1448) - Apply Clippy suggestions and enforce (#1448, #1474) - Move all benchmarks to new `benches` crate (#1329, #1439) and migrate to Criterion (#1490) +### Documentation +- Improve `ThreadRng` related docs (#1257) +- Docs: enable experimental `--generate-link-to-definition` feature (#1327) +- Better doc of crate features, use `doc_auto_cfg` (#1411, #1450) + ## [0.8.5] - 2021-08-20 ### Fixes - Fix build on non-32/64-bit architectures (#1144) diff --git a/Cargo.toml b/Cargo.toml index d8a7e77c72c..956f12741fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.9.0-beta.3" +version = "0.9.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -72,14 +72,14 @@ members = [ exclude = ["benches", "distr_test"] [dependencies] -rand_core = { path = "rand_core", version = "=0.9.0-beta.1", default-features = false } +rand_core = { path = "rand_core", version = "0.9.0", default-features = false } log = { version = "0.4.4", optional = true } serde = { version = "1.0.103", features = ["derive"], optional = true } -rand_chacha = { path = "rand_chacha", version = "=0.9.0-beta.1", default-features = false, optional = true } +rand_chacha = { path = "rand_chacha", version = "0.9.0", default-features = false, optional = true } zerocopy = { version = "0.8.0", default-features = false, features = ["simd"] } [dev-dependencies] -rand_pcg = { path = "rand_pcg", version = "=0.9.0-beta.1" } +rand_pcg = { path = "rand_pcg", version = "0.9.0" } # Only to test serde bincode = "1.2.1" rayon = "1.7" diff --git a/README.md b/README.md index 524c2e8047a..740807a9669 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ All with: Rand **is not**: -- Small (LOC). Most low-level crates are small, but the higher-level `rand` +- Small (LoC). Most low-level crates are small, but the higher-level `rand` and `rand_distr` each contain a lot of functionality. - Simple (implementation). We have a strong focus on correctness, speed and flexibility, but not simplicity. If you prefer a small-and-simple library, there are @@ -53,29 +53,12 @@ Documentation: - [API reference (docs.rs)](https://docs.rs/rand) -## Usage - -Add this to your `Cargo.toml`: -```toml -[dependencies] -rand = "0.8.5" -``` - -Or, to try the 0.9.0 beta release: -```toml -[dependencies] -rand = "=0.9.0-beta.3" -``` - -To get started using Rand, see [The Book](https://rust-random.github.io/book). - ## Versions Rand is *mature* (suitable for general usage, with infrequent breaking releases which minimise breakage) but not yet at 1.0. Current versions are: -- Version 0.8 was released in December 2020 with many small changes. -- Version 0.9 is in development with many small changes. +- Version 0.9 was released in January 2025. See the [CHANGELOG](CHANGELOG.md) or [Upgrade Guide](https://rust-random.github.io/book/update.html) for more details. diff --git a/distr_test/Cargo.toml b/distr_test/Cargo.toml index 54b30e754b1..d9d7fe2c274 100644 --- a/distr_test/Cargo.toml +++ b/distr_test/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" publish = false [dev-dependencies] -rand_distr = { path = "../rand_distr", version = "=0.5.0-beta.3", default-features = false, features = ["alloc"] } -rand = { path = "..", version = "=0.9.0-beta.3", features = ["small_rng"] } +rand_distr = { path = "../rand_distr", version = "0.5.0", default-features = false, features = ["alloc"] } +rand = { path = "..", version = "0.9.0", features = ["small_rng"] } num-traits = "0.2.19" # Special functions for testing distributions special = "0.11.0" diff --git a/rand_chacha/CHANGELOG.md b/rand_chacha/CHANGELOG.md index 4f33c2cde71..7965cf7640e 100644 --- a/rand_chacha/CHANGELOG.md +++ b/rand_chacha/CHANGELOG.md @@ -4,17 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.9.0-beta.1] - 2024-11-30 -- Bump `rand_core` version - -## [0.9.0-beta.0] - 2024-11-25 -This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). +## [0.9.0] - 2025-01-27 +### Dependencies and features +- Update to `rand_core` v0.9.0 (#1558) +- Feature `std` now implies feature `rand_core/std` (#1153) +- Rename feature `serde1` to `serde` (#1477) +- Rename feature `getrandom` to `os_rng` (#1537) -- Made `rand_chacha` propagate the `std` feature down to `rand_core` (#1153) +### Other changes - Remove usage of `unsafe` in `fn generate` (#1181) then optimise for AVX2 (~4-7%) (#1192) -- The `serde1` feature has been renamed `serde` (#1477) - Revise crate docs (#1454) -- Rename feature `getrandom` to `os_rng` (#1537) ## [0.3.1] - 2021-06-09 - add getters corresponding to existing setters: `get_seed`, `get_stream` (#1124) diff --git a/rand_chacha/Cargo.toml b/rand_chacha/Cargo.toml index 6438e833c51..7052dd48e4b 100644 --- a/rand_chacha/Cargo.toml +++ b/rand_chacha/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_chacha" -version = "0.9.0-beta.1" +version = "0.9.0" authors = ["The Rand Project Developers", "The Rust Project Developers", "The CryptoCorrosion Contributors"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -20,14 +20,14 @@ all-features = true rustdoc-args = ["--generate-link-to-definition"] [dependencies] -rand_core = { path = "../rand_core", version = "=0.9.0-beta.1" } +rand_core = { path = "../rand_core", version = "0.9.0" } ppv-lite86 = { version = "0.2.14", default-features = false, features = ["simd"] } serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] # Only to test serde serde_json = "1.0" -rand_core = { path = "../rand_core", version = "=0.9.0-beta.1", features = ["os_rng"] } +rand_core = { path = "../rand_core", version = "0.9.0", features = ["os_rng"] } [features] default = ["std"] diff --git a/rand_core/CHANGELOG.md b/rand_core/CHANGELOG.md index 327a98c095e..3b3064db71b 100644 --- a/rand_core/CHANGELOG.md +++ b/rand_core/CHANGELOG.md @@ -4,24 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.9.0-beta.1] - 2024-11-30 -- Update to `getrandom` v0.3.0-rc.0 - -## [0.9.0-beta.0] - 2024-11-25 -This is a pre-release. To depend on this version, use `rand_core = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). - -### Compilation options and unsafe +## [0.9.0] - 2025-01-27 +### Dependencies and features - Bump the MSRV to 1.63.0 (#1207, #1246, #1269, #1341, #1416, #1536); note that 1.60.0 may work for dependents when using `--ignore-rust-version` -- The `serde1` feature has been renamed `serde` (#1477) +- Update to `getrandom` v0.3.0 (#1558) - Use `zerocopy` to replace some `unsafe` code (#1349, #1393, #1446, #1502) +- Rename feature `serde1` to `serde` (#1477) +- Rename feature `getrandom` to `os_rng` (#1537) -### Other +### API changes - Allow `rand_core::impls::fill_via_u*_chunks` to mutate source (#1182) - Add fn `RngCore::read_adapter` implementing `std::io::Read` (#1267) - Add trait `CryptoBlockRng: BlockRngCore`; make `trait CryptoRng: RngCore` (#1273) - Add traits `TryRngCore`, `TryCryptoRng` (#1424, #1499) +- Rename `fn SeedableRng::from_rng` -> `try_from_rng` and add infallible variant `fn from_rng` (#1424) +- Rename `fn SeedableRng::from_entropy` -> `from_os_rng` and add fallible variant `fn try_from_os_rng` (#1424) - Add bounds `Clone` and `AsRef` to associated type `SeedableRng::Seed` (#1491) -- Rename feature `getrandom` to `os_rng` (#1537) ## [0.6.4] - 2022-09-15 - Fix unsoundness in `::next_u32` (#1160) diff --git a/rand_core/Cargo.toml b/rand_core/Cargo.toml index c932b9b88fe..d1d9e66d7fa 100644 --- a/rand_core/Cargo.toml +++ b/rand_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_core" -version = "0.9.0-beta.1" +version = "0.9.0" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -31,5 +31,5 @@ serde = ["dep:serde"] # enables serde for BlockRng wrapper [dependencies] serde = { version = "1", features = ["derive"], optional = true } -getrandom = { version = "0.3.0-rc.0", optional = true } +getrandom = { version = "0.3.0", optional = true } zerocopy = { version = "0.8.0", default-features = false } diff --git a/rand_core/src/lib.rs b/rand_core/src/lib.rs index babc0e349aa..9faff9c752f 100644 --- a/rand_core/src/lib.rs +++ b/rand_core/src/lib.rs @@ -205,19 +205,18 @@ pub trait CryptoRng: RngCore {} impl CryptoRng for T where T::Target: CryptoRng {} -/// A potentially fallible version of [`RngCore`]. +/// A potentially fallible variant of [`RngCore`] /// -/// This trait is primarily used for IO-based generators such as [`OsRng`]. +/// This trait is a generalization of [`RngCore`] to support potentially- +/// fallible IO-based generators such as [`OsRng`]. /// -/// Most of higher-level generic code in the `rand` crate is built on top -/// of the the [`RngCore`] trait. Users can transform a fallible RNG -/// (i.e. [`TryRngCore`] implementor) into an "infallible" (but potentially -/// panicking) RNG (i.e. [`RngCore`] implementor) using the [`UnwrapErr`] wrapper. +/// All implementations of [`RngCore`] automatically support this `TryRngCore` +/// trait, using [`Infallible`][core::convert::Infallible] as the associated +/// `Error` type. /// -/// [`RngCore`] implementors also usually implement [`TryRngCore`] with the `Error` -/// associated type being equal to [`Infallible`][core::convert::Infallible]. -/// In other words, users can use [`TryRngCore`] to generalize over fallible and -/// infallible RNGs. +/// An implementation of this trait may be made compatible with code requiring +/// an [`RngCore`] through [`TryRngCore::unwrap_err`]. The resulting RNG will +/// panic in case the underlying fallible RNG yields an error. pub trait TryRngCore { /// The type returned in the event of a RNG error. type Error: fmt::Debug + fmt::Display; diff --git a/rand_distr/CHANGELOG.md b/rand_distr/CHANGELOG.md index ee3490ca30d..81fa3a3c4bc 100644 --- a/rand_distr/CHANGELOG.md +++ b/rand_distr/CHANGELOG.md @@ -4,8 +4,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.5.0-beta.3] - 2025-01-03 -- Bump `rand` version (#1547) +## [0.5.0] - 2025-01-27 + +### Dependencies and features +- Bump the MSRV to 1.61.0 (#1207, #1246, #1269, #1341, #1416); note that 1.60.0 may work for dependents when using `--ignore-rust-version` +- Update to `rand` v0.9.0 (#1558) +- Rename feature `serde1` to `serde` (#1477) + +### API changes +- Make distributions comparable with `PartialEq` (#1218) +- `Dirichlet` now uses `const` generics, which means that its size is required at compile time (#1292) +- The `Dirichlet::new_with_size` constructor was removed (#1292) +- Add `WeightedIndexTree` (#1372, #1444) +- Add `PertBuilder` to allow specification of `mean` or `mode` (#1452) +- Rename `Zeta`'s parameter `a` to `s` (#1466) +- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480) +- Remove support for usage of `isize` as a `WeightedAliasIndex` weight (#1487) +- Change parameter type of `Zipf::new`: `n` is now floating-point (#1518) + +### API changes: renames - Move `Slice` -> `slice::Choose`, `EmptySlice` -> `slice::Empty` (#1548) - Rename trait `DistString` -> `SampleString` (#1548) - Rename `DistIter` -> `Iter`, `DistMap` -> `Map` (#1548) @@ -13,20 +30,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Move `weighted_alias::{AliasableWeight, WeightedAliasIndex}` -> `weighted::{..}` (#1548) - Move `weighted_tree::WeightedTreeIndex` -> `weighted::WeightedTreeIndex` (#1548) -## [0.5.0-beta.2] - 2024-11-30 -- Bump `rand` version - -## [0.5.0-beta.1] - 2024-11-27 -- Fix docs.rs build (#1539) - -## [0.5.0-beta.0] - 2024-11-25 -This is a pre-release. To depend on this version, use `rand = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). - -### Compilation options -- Target `rand` version `0.9.0-beta.0` -- Bump the MSRV to 1.61.0 (#1207, #1246, #1269, #1341, #1416); note that 1.60.0 may work for dependents when using `--ignore-rust-version` -- The `serde1` feature has been renamed `serde` (#1477) - ### Testing - Add Kolmogorov Smirnov tests for distributions (#1494, #1504, #1525, #1530) @@ -35,31 +38,16 @@ This is a pre-release. To depend on this version, use `rand = "=0.9.0-beta.0"` t - Fix `Poisson` distribution instantiation so it return an error if lambda is infinite (#1291) - Fix Dirichlet sample for small alpha values to avoid NaN samples (#1209) - Fix infinite loop in `Binomial` distribution (#1325) +- Fix `Pert` distribution where `mode` is close to `(min + max) / 2` (#1452) - Fix panic in Binomial (#1484) - Limit the maximal acceptable lambda for `Poisson` to solve (#1312) (#1498) - Fix bug in `Hypergeometric`, this is a Value-breaking change (#1510) -### Additions -- Make distributions comparable with `PartialEq` (#1218) -- Add `WeightedIndexTree` (#1372, #1444) - -### Changes ### Other changes - Remove unused fields from `Gamma`, `NormalInverseGaussian` and `Zipf` distributions (#1184) This breaks serialization compatibility with older versions. -- `Dirichlet` now uses `const` generics, which means that its size is required at compile time (#1292) -- The `Dirichlet::new_with_size` constructor was removed (#1292) -- Add `PertBuilder`, fix case where mode ≅ mean (#1452) -- Rename `Zeta`'s parameter `a` to `s` (#1466) -- Mark `WeightError`, `PoissonError`, `BinomialError` as `#[non_exhaustive]` (#1480) -- Remove support for usage of `isize` as a `WeightedAliasIndex` weight (#1487) -- Change parameter type of `Zipf::new`: `n` is now floating-point (#1518) - -### Optimizations -- Move some of the computations in Binomial from `sample` to `new` (#1484) - -### Documentation - Add plots for `rand_distr` distributions to documentation (#1434) +- Move some of the computations in Binomial from `sample` to `new` (#1484) ## [0.4.3] - 2021-12-30 - Fix `no_std` build (#1208) diff --git a/rand_distr/Cargo.toml b/rand_distr/Cargo.toml index 7ba52f95046..dd55673777c 100644 --- a/rand_distr/Cargo.toml +++ b/rand_distr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_distr" -version = "0.5.0-beta.3" +version = "0.5.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -33,15 +33,15 @@ std_math = ["num-traits/std"] serde = ["dep:serde", "dep:serde_with", "rand/serde"] [dependencies] -rand = { path = "..", version = "=0.9.0-beta.3", default-features = false } +rand = { path = "..", version = "0.9.0", default-features = false } num-traits = { version = "0.2", default-features = false, features = ["libm"] } serde = { version = "1.0.103", features = ["derive"], optional = true } serde_with = { version = ">= 3.0, <= 3.11", optional = true } [dev-dependencies] -rand_pcg = { version = "=0.9.0-beta.1", path = "../rand_pcg" } +rand_pcg = { version = "0.9.0", path = "../rand_pcg" } # For inline examples -rand = { path = "..", version = "=0.9.0-beta.3", features = ["small_rng"] } +rand = { path = "..", version = "0.9.0", features = ["small_rng"] } # Histogram implementation for testing uniformity average = { version = "0.15", features = [ "std" ] } # Special functions for testing distributions diff --git a/rand_pcg/CHANGELOG.md b/rand_pcg/CHANGELOG.md index f3e50819c25..bab1cd0e8c8 100644 --- a/rand_pcg/CHANGELOG.md +++ b/rand_pcg/CHANGELOG.md @@ -4,17 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.9.0-beta.1] - 2024-11-30 -- Bump `rand_core` version - -## [0.9.0-beta.0] - 2024-11-25 -This is a pre-release. To depend on this version, use `rand_chacha = "=0.9.0-beta.0"` to prevent automatic updates (which can be expected to include breaking changes). +## [0.9.0] - 2025-01-27 +### Dependencies and features +- Update to `rand_core` v0.9.0 (#1558) +- Rename feature `serde1` to `serde` (#1477) +- Rename feature `getrandom` to `os_rng` (#1537) -- The `serde1` feature has been renamed `serde` (#1477) +### Other changes - Add `Lcg128CmDxsm64` generator compatible with NumPy's `PCG64DXSM` (#1202) -- Add examples for initializing the RNGs +- Add examples for initializing the RNGs (#1352) - Revise crate docs (#1454) -- Rename feature `getrandom` to `os_rng` (#1537) ## [0.3.1] - 2021-06-15 - Add `advance` methods to RNGs (#1111) diff --git a/rand_pcg/Cargo.toml b/rand_pcg/Cargo.toml index 3ba620a7a63..74740950712 100644 --- a/rand_pcg/Cargo.toml +++ b/rand_pcg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand_pcg" -version = "0.9.0-beta.1" +version = "0.9.0" authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -24,7 +24,7 @@ serde = ["dep:serde"] os_rng = ["rand_core/os_rng"] [dependencies] -rand_core = { path = "../rand_core", version = "=0.9.0-beta.1" } +rand_core = { path = "../rand_core", version = "0.9.0" } serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] @@ -32,4 +32,4 @@ serde = { version = "1", features = ["derive"], optional = true } # deps yet, see: https://github.com/rust-lang/cargo/issues/1596 # Versions prior to 1.1.4 had incorrect minimal dependencies. bincode = { version = "1.1.4" } -rand_core = { path = "../rand_core", version = "=0.9.0-beta.1", features = ["os_rng"] } +rand_core = { path = "../rand_core", version = "0.9.0", features = ["os_rng"] }