From 448524fb3a006c8dc0d64e5f8b8432b335e43fb8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 14 Sep 2019 09:26:10 +0100 Subject: [PATCH 01/11] Add notice of future removal of getrandom's wasm features --- Cargo.toml | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index ebd4c8972aa..2611ecb0d13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ serde1 = [] # does nothing, deprecated std = ["rand_core/std", "rand_chacha/std", "alloc", "getrandom"] alloc = ["rand_core/alloc"] # enables Vec and Box support (without std) # re-export optional WASM dependencies to avoid breakage: +# Warning: wasm-bindgen and stdweb features will be removed in rand 0.8; +# recommended to activate via the getrandom crate instead. wasm-bindgen = ["getrandom_package/wasm-bindgen"] stdweb = ["getrandom_package/stdweb"] getrandom = ["getrandom_package", "rand_core/getrandom"] diff --git a/README.md b/README.md index 5acbadb3acc..66100fc712b 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,10 @@ Optionally, the following dependencies can be enabled: - `log` enables logging via the `log` crate - `stdweb` implies `getrandom/stdweb` to enable `getrandom` support on `wasm32-unknown-unknown` + (will be removed in rand 0.8; activate via `getrandom` crate instead) - `wasm-bindgen` implies `getrandom/wasm-bindgen` to enable `getrandom` support on `wasm32-unknown-unknown` + (will be removed in rand 0.8; activate via `getrandom` crate instead) Additionally, these features configure Rand: From 70c89b17070706679a42f83e441ee08bdd3870db Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 10:16:29 +0100 Subject: [PATCH 02/11] Add value_stability tests to WeightedIndex distributions --- src/distributions/weighted/alias_method.rs | 18 ++++++++++++++ src/distributions/weighted/mod.rs | 29 ++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/distributions/weighted/alias_method.rs b/src/distributions/weighted/alias_method.rs index bdd4ba0f3c9..1ba0ea21a19 100644 --- a/src/distributions/weighted/alias_method.rs +++ b/src/distributions/weighted/alias_method.rs @@ -496,4 +496,22 @@ mod test { WeightedError::InvalidWeight ); } + + #[test] + fn value_stability() { + fn test_samples(weights: Vec, buf: &mut [usize], expected: &[usize]) { + assert_eq!(buf.len(), expected.len()); + let distr = WeightedIndex::new(weights).unwrap(); + let mut rng = crate::test::rng(0x9c9fa0b0580a7031); + for r in buf.iter_mut() { + *r = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + 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]); + } } diff --git a/src/distributions/weighted/mod.rs b/src/distributions/weighted/mod.rs index 27116375fd4..720859bd176 100644 --- a/src/distributions/weighted/mod.rs +++ b/src/distributions/weighted/mod.rs @@ -316,6 +316,35 @@ mod test { assert_eq!(distr.cumulative_weights, expected_distr.cumulative_weights); } } + + #[test] + fn value_stability() { + 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(); + let mut rng = crate::test::rng(701); + for r in buf.iter_mut() { + *r = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + 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]); + } } /// Error type returned from `WeightedIndex::new`. From 72af6f84f26daca5f8248be52f687fe734b2f79a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 10:51:54 +0100 Subject: [PATCH 03/11] Add value_stability tests for bernoulli, int, float and other distrs --- src/distributions/bernoulli.rs | 11 ++++++ src/distributions/float.rs | 47 ++++++++++++++++++++++--- src/distributions/integer.rs | 63 ++++++++++++++++++++++++++++++++-- src/distributions/other.rs | 38 ++++++++++++++++++-- 4 files changed, 150 insertions(+), 9 deletions(-) diff --git a/src/distributions/bernoulli.rs b/src/distributions/bernoulli.rs index eadd0563b05..614d8429498 100644 --- a/src/distributions/bernoulli.rs +++ b/src/distributions/bernoulli.rs @@ -163,4 +163,15 @@ mod test { let avg2 = (sum2 as f64) / (N as f64); assert!((avg2 - (NUM as f64)/(DENOM as f64)).abs() < 5e-3); } + + #[test] + fn value_stability() { + let mut rng = crate::test::rng(3); + let distr = Bernoulli::new(0.4532).unwrap(); + let mut buf = [false; 10]; + for x in &mut buf { + *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 bda523ad485..bef18a5b560 100644 --- a/src/distributions/float.rs +++ b/src/distributions/float.rs @@ -168,11 +168,8 @@ float_impls! { f64x8, u64x8, f64, u64, 52, 1023 } #[cfg(test)] mod tests { - use crate::Rng; - use crate::distributions::{Open01, OpenClosed01}; + use super::*; use crate::rngs::mock::StepRng; - #[cfg(feature="simd_support")] - use packed_simd::*; const EPSILON32: f32 = ::core::f32::EPSILON; const EPSILON64: f64 = ::core::f64::EPSILON; @@ -256,4 +253,46 @@ mod tests { test_f64! { f64x4_edge_cases, f64x4, f64x4::splat(0.0), f64x4::splat(EPSILON64) } #[cfg(feature="simd_support")] test_f64! { f64x8_edge_cases, f64x8, f64x8::splat(0.0), f64x8::splat(EPSILON64) } + + #[test] + fn value_stability() { + fn test_samples>( + distr: &D, zero: T, expected: &[T] + ) { + let mut rng = crate::test::rng(0x6f44f5646c2a7334); + let mut buf = [zero; 3]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(&buf, expected); + } + + test_samples(&Standard, 0f32, &[0.0035963655, 0.7346052, 0.09778172]); + 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(&Open01, 0f32, &[0.0035963655, 0.73460525, 0.09778172]); + test_samples(&Open01, 0f64, &[0.7346051961657584, + 0.20298547462974248, 0.8166436635290656]); + + #[cfg(feature="simd_support")] { + // We only test a sub-set of types here. Values are identical to + // 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, f64x2::new(0.0, 0.0), &[ + f64x2::new(0.7346051961657583, 0.20298547462974248), + f64x2::new(0.8166436635290655, 0.7423708925400552), + f64x2::new(0.16387782224016323, 0.9087068770169618)]); + } + } } diff --git a/src/distributions/integer.rs b/src/distributions/integer.rs index 5238339a026..63cd3b9489b 100644 --- a/src/distributions/integer.rs +++ b/src/distributions/integer.rs @@ -158,8 +158,7 @@ simd_impl!((__m64, u8x8), (__m128i, u8x16), (__m256i, u8x32),); #[cfg(test)] mod tests { - use crate::Rng; - use crate::distributions::{Standard}; + use super::*; #[test] fn test_integers() { @@ -181,4 +180,64 @@ mod tests { #[cfg(not(target_os = "emscripten"))] rng.sample::(Standard); } + + #[test] + fn value_stability() { + fn test_samples( + zero: T, expected: &[T] + ) + where Standard: Distribution + { + let mut rng = crate::test::rng(807); + let mut buf = [zero; 3]; + for x in &mut buf { + *x = rng.sample(Standard); + } + assert_eq!(&buf, expected); + } + + 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]); + #[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 + + #[cfg(feature="simd_support")] { + // 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)]); + 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)]); + + test_samples(i64x8::default(), &[ + i64x8::new(-7387126082252079607, -2350127744969763473, + 1487364411147516184, 7895421560427121838, + 602190064936008898, 6022086574635100741, + -5080089175222015595, -4066367846667249123), + i64x8::new(9180885022207963908, 3095981199532211089, + 6586075293021332726, 419343203796414657, + 3186951873057035255, 5287129228749947252, + 444726432079249540, -1587028029513790706), + i64x8::new(6075236523189346388, 1351763722368165432, + -6192309979959753740, -7697775502176768592, + -4482022114172078123, 7522501477800909500, + -1837258847956201231, -586926753024886735)]); + } + } } diff --git a/src/distributions/other.rs b/src/distributions/other.rs index 6ec04734842..8c0e477b21e 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -177,9 +177,8 @@ impl Distribution> for Standard where Standard: Distribution { #[cfg(test)] mod tests { - use crate::{Rng, RngCore, Standard}; - use crate::distributions::Alphanumeric; - #[cfg(all(not(feature="std"), feature="alloc"))] use alloc::string::String; + use super::*; + use crate::RngCore; #[test] fn test_misc() { @@ -217,4 +216,37 @@ mod tests { } assert!(incorrect == false); } + + #[test] + fn value_stability() { + fn test_samples>( + distr: &D, zero: T, expected: &[T]) { + let mut rng = crate::test::rng(807); + let mut buf = [zero; 5]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(&buf, expected); + } + + test_samples(&Standard, 'a', &['\u{8cdac}', '\u{a346a}', '\u{80120}', '\u{ed692}', '\u{35888}']); + test_samples(&Alphanumeric, 'a', &['h', 'm', 'e', '3', 'M']); + test_samples(&Standard, false, &[true, true, false, true, false]); + test_samples(&Standard, Option::::None, + &[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,), (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]]); + } } From 856fdfb4331188e3fd91ab8b31a11df86b6122b9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 12:01:05 +0100 Subject: [PATCH 04/11] Distribution: add doc of portability --- src/distributions/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 02ece6f98ef..f43169958e5 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -173,6 +173,12 @@ mod utils; /// 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::method.sample_iter pub trait Distribution { /// Generate a random value of `T`, using `rng` as the source of randomness. From f78781aa84142f95f6397431958fc60bef555ca0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 12:01:43 +0100 Subject: [PATCH 05/11] Uniform: replace inner field with struct tuple --- src/distributions/uniform.rs | 48 ++++++++++++++---------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 8c90f4e4bd2..498cd5df701 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -66,9 +66,7 @@ //! struct MyF32(f32); //! //! #[derive(Clone, Copy, Debug)] -//! struct UniformMyF32 { -//! inner: UniformFloat, -//! } +//! struct UniformMyF32(UniformFloat); //! //! impl UniformSampler for UniformMyF32 { //! type X = MyF32; @@ -76,9 +74,7 @@ //! where B1: SampleBorrow + Sized, //! B2: SampleBorrow + Sized //! { -//! UniformMyF32 { -//! inner: UniformFloat::::new(low.borrow().0, high.borrow().0), -//! } +//! UniformMyF32(UniformFloat::::new(low.borrow().0, high.borrow().0)) //! } //! fn new_inclusive(low: B1, high: B2) -> Self //! where B1: SampleBorrow + Sized, @@ -87,7 +83,7 @@ //! UniformSampler::new(low, high) //! } //! fn sample(&self, rng: &mut R) -> Self::X { -//! MyF32(self.inner.sample(rng)) +//! MyF32(self.0.sample(rng)) //! } //! } //! @@ -166,9 +162,7 @@ use packed_simd::*; /// [`new`]: Uniform::new /// [`new_inclusive`]: Uniform::new_inclusive #[derive(Clone, Copy, Debug)] -pub struct Uniform { - inner: X::Sampler, -} +pub struct Uniform(X::Sampler); impl Uniform { /// Create a new `Uniform` instance which samples uniformly from the half @@ -177,7 +171,7 @@ impl Uniform { where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { - Uniform { inner: X::Sampler::new(low, high) } + Uniform(X::Sampler::new(low, high)) } /// Create a new `Uniform` instance which samples uniformly from the closed @@ -186,13 +180,13 @@ impl Uniform { where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { - Uniform { inner: X::Sampler::new_inclusive(low, high) } + Uniform(X::Sampler::new_inclusive(low, high)) } } impl Distribution for Uniform { fn sample(&self, rng: &mut R) -> X { - self.inner.sample(rng) + self.0.sample(rng) } } @@ -1211,18 +1205,14 @@ mod tests { x: f32, } #[derive(Clone, Copy, Debug)] - struct UniformMyF32 { - inner: UniformFloat, - } + struct UniformMyF32(UniformFloat); impl UniformSampler for UniformMyF32 { type X = MyF32; fn new(low: B1, high: B2) -> Self where B1: SampleBorrow + Sized, B2: SampleBorrow + Sized { - UniformMyF32 { - inner: UniformFloat::::new(low.borrow().x, high.borrow().x), - } + UniformMyF32(UniformFloat::::new(low.borrow().x, high.borrow().x)) } fn new_inclusive(low: B1, high: B2) -> Self where B1: SampleBorrow + Sized, @@ -1231,7 +1221,7 @@ mod tests { UniformSampler::new(low, high) } fn sample(&self, rng: &mut R) -> Self::X { - MyF32 { x: self.inner.sample(rng) } + MyF32 { x: self.0.sample(rng) } } } impl SampleUniform for MyF32 { @@ -1250,21 +1240,21 @@ mod tests { #[test] fn test_uniform_from_std_range() { let r = Uniform::from(2u32..7); - assert_eq!(r.inner.low, 2); - assert_eq!(r.inner.range, 5); + assert_eq!(r.0.low, 2); + assert_eq!(r.0.range, 5); let r = Uniform::from(2.0f64..7.0); - assert_eq!(r.inner.low, 2.0); - assert_eq!(r.inner.scale, 5.0); + 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); - assert_eq!(r.inner.low, 2); - assert_eq!(r.inner.range, 5); + assert_eq!(r.0.low, 2); + assert_eq!(r.0.range, 5); let r = Uniform::from(2.0f64..=7.0); - assert_eq!(r.inner.low, 2.0); - assert!(r.inner.scale > 5.0); - assert!(r.inner.scale < 5.0 + 1e-14); + assert_eq!(r.0.low, 2.0); + assert!(r.0.scale > 5.0); + assert!(r.0.scale < 5.0 + 1e-14); } } From 096a5dd5d69fe8a505507c93ab274b3fcacef583 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 12:17:36 +0100 Subject: [PATCH 06/11] UniformSampler: add doc on usage of sample_single --- src/distributions/uniform.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 498cd5df701..b6125e73c39 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -245,6 +245,17 @@ pub trait UniformSampler: Sized { /// 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) + /// } + /// ``` fn sample_single(low: B1, high: B2, rng: &mut R) -> Self::X where B1: SampleBorrow + Sized, From 2ef2e669b600e8445bbdf38d715afd3b1f389488 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 12:31:41 +0100 Subject: [PATCH 07/11] Add value_stability tests for uniform --- src/distributions/uniform.rs | 43 ++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index b6125e73c39..a12305c35bf 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -934,11 +934,8 @@ impl UniformSampler for UniformDuration { #[cfg(test)] mod tests { - use crate::Rng; + use super::*; use crate::rngs::mock::StepRng; - use crate::distributions::uniform::Uniform; - use crate::distributions::utils::FloatAsSIMD; - #[cfg(feature="simd_support")] use packed_simd::*; #[should_panic] #[test] @@ -1268,4 +1265,42 @@ mod tests { assert!(r.0.scale > 5.0); assert!(r.0.scale < 5.0 + 1e-14); } + + #[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); + } + assert_eq!(&buf, expected_single); + + let distr = Uniform::new(lb, ub); + 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)]); + } } From a43d7f1166ede87bfce92ae236bde95c486bfd9c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 13:54:09 +0100 Subject: [PATCH 08/11] Fix tests --- 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 8c0e477b21e..334b6f81a5b 100644 --- a/src/distributions/other.rs +++ b/src/distributions/other.rs @@ -179,6 +179,7 @@ impl Distribution> for Standard where Standard: Distribution { mod tests { use super::*; use crate::RngCore; + #[cfg(all(not(feature="std"), feature="alloc"))] use alloc::string::String; #[test] fn test_misc() { @@ -232,7 +233,7 @@ mod tests { test_samples(&Standard, 'a', &['\u{8cdac}', '\u{a346a}', '\u{80120}', '\u{ed692}', '\u{35888}']); test_samples(&Alphanumeric, 'a', &['h', 'm', 'e', '3', 'M']); test_samples(&Standard, false, &[true, true, false, true, false]); - test_samples(&Standard, Option::::None, + 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), From 802ba8f7f54635cdc009d906f032392dda77faa3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 17:04:21 +0100 Subject: [PATCH 09/11] rand_distr: add missing value_stability tests --- rand_distr/src/binomial.rs | 18 +++++++++++ rand_distr/src/cauchy.rs | 21 +++++++++++-- rand_distr/src/dirichlet.rs | 13 ++++++-- rand_distr/src/exponential.rs | 27 ++++++++++++++-- rand_distr/src/gamma.rs | 59 +++++++++++++++++++++++++++++++++-- rand_distr/src/normal.rs | 35 +++++++++++++++++++-- rand_distr/src/pareto.rs | 23 ++++++++++++-- rand_distr/src/poisson.rs | 22 +++++++++++-- rand_distr/src/weibull.rs | 23 ++++++++++++-- 9 files changed, 225 insertions(+), 16 deletions(-) diff --git a/rand_distr/src/binomial.rs b/rand_distr/src/binomial.rs index 0e6bf9a1e18..9c2b110ae5a 100644 --- a/rand_distr/src/binomial.rs +++ b/rand_distr/src/binomial.rs @@ -326,4 +326,22 @@ mod test { fn test_binomial_invalid_lambda_neg() { Binomial::new(20, -10.0).unwrap(); } + + #[test] + fn value_stability() { + fn test_samples(n: u64, p: f64, expected: &[u64]) { + let distr = Binomial::new(n, p).unwrap(); + let mut rng = crate::test::rng(353); + let mut buf = [0; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + // We have multiple code paths: np < 10, p > 0.5 + test_samples(2, 0.7, &[1, 1, 2, 1]); + test_samples(20, 0.3, &[7, 7, 5, 7]); + test_samples(2000, 0.6, &[1194, 1208, 1192, 1210]); + } } diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 6b0e7c6cf33..8932a8e7787 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -72,8 +72,7 @@ where Standard: Distribution #[cfg(test)] mod test { - use crate::Distribution; - use super::Cauchy; + use super::*; fn median(mut numbers: &mut [f64]) -> f64 { sort(&mut numbers); @@ -117,4 +116,22 @@ mod test { fn test_cauchy_invalid_scale_neg() { Cauchy::new(0.0, -10.0).unwrap(); } + + #[test] + fn value_stability() { + fn test_samples(m: N, s: N, expected: &[N]) + where Standard: Distribution { + let distr = Cauchy::new(m, s).unwrap(); + let mut rng = crate::test::rng(353); + let mut buf = [m; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + test_samples(100f64, 10.0, &[77.93369152808678, 90.1606912098641, + 125.31516221323625, 86.10217834773925]); + test_samples(20f32, 10.0, &[27.175842, -2.0663052, 11.013268, 10.160688]); + } } diff --git a/rand_distr/src/dirichlet.rs b/rand_distr/src/dirichlet.rs index 71cf73c9033..cba063ae755 100644 --- a/rand_distr/src/dirichlet.rs +++ b/rand_distr/src/dirichlet.rs @@ -107,8 +107,7 @@ where StandardNormal: Distribution, Exp1: Distribution, Open01: Distributi #[cfg(test)] mod test { - use super::Dirichlet; - use crate::Distribution; + use super::*; #[test] fn test_dirichlet() { @@ -151,4 +150,14 @@ mod test { fn test_dirichlet_invalid_alpha() { Dirichlet::new_with_size(0.0f64, 2).unwrap(); } + + #[test] + fn value_stability() { + let mut rng = crate::test::rng(223); + assert_eq!(rng.sample(Dirichlet::new(vec![1.0, 2.0, 3.0]).unwrap()), + vec![0.12941567177708177, 0.4702121891675036, 0.4003721390554146]); + assert_eq!(rng.sample(Dirichlet::new_with_size(8.0, 5).unwrap()), + vec![0.17684200044809556, 0.29915953935953055, + 0.1832858056608014, 0.1425623503573967, 0.19815030417417595]); + } } diff --git a/rand_distr/src/exponential.rs b/rand_distr/src/exponential.rs index 83224890b05..8695252a53f 100644 --- a/rand_distr/src/exponential.rs +++ b/rand_distr/src/exponential.rs @@ -121,8 +121,7 @@ where Exp1: Distribution #[cfg(test)] mod test { - use crate::Distribution; - use super::Exp; + use super::*; #[test] fn test_exp() { @@ -142,4 +141,28 @@ mod test { fn test_exp_invalid_lambda_neg() { Exp::new(-10.0).unwrap(); } + + #[test] + fn value_stability() { + fn test_samples> + (distr: D, zero: N, expected: &[N]) + { + let mut rng = crate::test::rng(223); + let mut buf = [zero; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + test_samples(Exp1, 0f32, &[1.079617, 1.8325565, 0.04601166, 0.34471703]); + test_samples(Exp1, 0f64, &[1.0796170642388276, 1.8325565304274, + 0.04601166186842716, 0.3447170217100157]); + + test_samples(Exp::new(2.0).unwrap(), 0f32, + &[0.5398085, 0.91627824, 0.02300583, 0.17235851]); + test_samples(Exp::new(1.0).unwrap(), 0f64, &[ + 1.0796170642388276, 1.8325565304274, + 0.04601166186842716, 0.3447170217100157]); + } } diff --git a/rand_distr/src/gamma.rs b/rand_distr/src/gamma.rs index 4018361648e..bbf861bd537 100644 --- a/rand_distr/src/gamma.rs +++ b/rand_distr/src/gamma.rs @@ -417,8 +417,7 @@ where StandardNormal: Distribution, Exp1: Distribution, Open01: Distributi #[cfg(test)] mod test { - use crate::Distribution; - use super::{Beta, ChiSquared, StudentT, FisherF}; + use super::*; #[test] fn test_chi_squared_one() { @@ -482,4 +481,60 @@ mod test { fn test_beta_invalid_dof() { Beta::new(0., 0.).unwrap(); } + + #[test] + fn value_stability() { + fn test_samples> + (distr: D, zero: N, expected: &[N]) + { + let mut rng = crate::test::rng(223); + let mut buf = [zero; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + // Gamma has 3 cases: shape == 1, shape < 1, shape > 1 + test_samples(Gamma::new(1.0, 5.0).unwrap(), 0f32, + &[5.398085, 9.162783, 0.2300583, 1.7235851]); + test_samples(Gamma::new(0.8, 5.0).unwrap(), 0f32, + &[0.5051203, 0.9048302, 3.095812, 1.8566116]); + test_samples(Gamma::new(1.1, 5.0).unwrap(), 0f64, &[ + 7.783878094584059, 1.4939528171618057, + 8.638017638857592, 3.0949337228829004]); + + // ChiSquared has 2 cases: k == 1, k != 1 + test_samples(ChiSquared::new(1.0).unwrap(), 0f64, &[ + 0.4893526200348249, 1.635249736808788, + 0.5013580219361969, 0.1457735613733489]); + test_samples(ChiSquared::new(0.1).unwrap(), 0f64, &[ + 0.014824404726978617, 0.021602123937134326, + 0.0000003431429746851693, 0.00000002291755769542258]); + test_samples(ChiSquared::new(10.0).unwrap(), 0f32, + &[12.693656, 6.812016, 11.082001, 12.436167]); + + // FisherF has same special cases as ChiSquared on each param + test_samples(FisherF::new(1.0, 13.5).unwrap(), 0f32, + &[0.32283646, 0.048049655, 0.0788893, 1.817178]); + test_samples(FisherF::new(1.0, 1.0).unwrap(), 0f32, + &[0.29925257, 3.4392934, 9.567652, 0.020074]); + test_samples(FisherF::new(0.7, 13.5).unwrap(), 0f64, &[ + 3.3196593155045124, 0.3409169916262829, + 0.03377989856426519, 0.00004041672861036937]); + + // StudentT has same special cases as ChiSquared + test_samples(StudentT::new(1.0).unwrap(), 0f32, + &[0.54703987, -1.8545331, 3.093162, -0.14168274]); + test_samples(StudentT::new(1.1).unwrap(), 0f64, &[ + 0.7729195887949754, 1.2606210611616204, + -1.7553606501113175, -2.377641221169782]); + + // Beta has same special cases as Gamma on each param + test_samples(Beta::new(1.0, 0.8).unwrap(), 0f32, + &[0.6444564, 0.357635, 0.4110078, 0.7347192]); + test_samples(Beta::new(0.7, 1.2).unwrap(), 0f64, &[ + 0.6433129944095513, 0.5373371199711573, + 0.10313293199269491, 0.002472280249144378]); + } } diff --git a/rand_distr/src/normal.rs b/rand_distr/src/normal.rs index 882754fae44..0413ceaf541 100644 --- a/rand_distr/src/normal.rs +++ b/rand_distr/src/normal.rs @@ -185,8 +185,7 @@ where StandardNormal: Distribution #[cfg(test)] mod tests { - use crate::Distribution; - use super::{Normal, LogNormal}; + use super::*; #[test] fn test_normal() { @@ -216,4 +215,36 @@ mod tests { fn test_log_normal_invalid_sd() { LogNormal::new(10.0, -1.0).unwrap(); } + + #[test] + fn value_stability() { + fn test_samples> + (distr: D, zero: N, expected: &[N]) + { + 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(StandardNormal, 0f32, + &[-0.11844189, 0.781378, 0.06563994, -1.1932899]); + test_samples(StandardNormal, 0f64, &[ + -0.11844188827977231, 0.7813779637772346, + 0.06563993969580051, -1.1932899004186373]); + + test_samples(Normal::new(0.0, 1.0).unwrap(), 0f32, + &[-0.11844189, 0.781378, 0.06563994, -1.1932899]); + test_samples(Normal::new(2.0, 0.5).unwrap(), 0f64, &[ + 1.940779055860114, 2.3906889818886174, + 2.0328199698479, 1.4033550497906813]); + + test_samples(LogNormal::new(0.0, 1.0).unwrap(), 0f32, + &[0.88830346, 2.1844804, 1.0678421, 0.30322206]); + test_samples(LogNormal::new(2.0, 0.5).unwrap(), 0f64, &[ + 6.964174338639032, 10.921015733601452, + 7.6355881556915906, 4.068828213584092]); + } } diff --git a/rand_distr/src/pareto.rs b/rand_distr/src/pareto.rs index 33ea382d383..ccbce9c2566 100644 --- a/rand_distr/src/pareto.rs +++ b/rand_distr/src/pareto.rs @@ -66,8 +66,7 @@ where OpenClosed01: Distribution #[cfg(test)] mod tests { - use crate::Distribution; - use super::Pareto; + use super::*; #[test] #[should_panic] @@ -86,4 +85,24 @@ mod tests { assert!(r >= scale); } } + + #[test] + fn value_stability() { + fn test_samples> + (distr: D, zero: N, expected: &[N]) + { + 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(Pareto::new(1.0, 1.0).unwrap(), 0f32, + &[1.0423688, 2.1235929, 4.132709, 1.4679428]); + test_samples(Pareto::new(2.0, 0.5).unwrap(), 0f64, &[ + 9.019295276219136, 4.3097126018270595, + 6.837815045397157, 105.8826669383772]); + } } diff --git a/rand_distr/src/poisson.rs b/rand_distr/src/poisson.rs index 4f4a0b7a3d9..d720ab4f413 100644 --- a/rand_distr/src/poisson.rs +++ b/rand_distr/src/poisson.rs @@ -134,8 +134,7 @@ where Standard: Distribution #[cfg(test)] mod test { - use crate::Distribution; - use super::Poisson; + use super::*; #[test] fn test_poisson_10() { @@ -230,4 +229,23 @@ mod test { fn test_poisson_invalid_lambda_neg() { Poisson::new(-10.0).unwrap(); } + + #[test] + fn value_stability() { + fn test_samples> + (distr: D, zero: N, expected: &[N]) + { + let mut rng = crate::test::rng(223); + let mut buf = [zero; 4]; + for x in &mut buf { + *x = rng.sample(&distr); + } + assert_eq!(buf, expected); + } + + // Special cases: < 12, >= 12 + test_samples(Poisson::new(7.0).unwrap(), 0f32, &[5.0, 11.0, 6.0, 5.0]); + test_samples(Poisson::new(7.0).unwrap(), 0f64, &[9.0, 5.0, 7.0, 6.0]); + test_samples(Poisson::new(27.0).unwrap(), 0f32, &[28.0, 32.0, 36.0, 36.0]); + } } diff --git a/rand_distr/src/weibull.rs b/rand_distr/src/weibull.rs index ddde3804d71..388503284fa 100644 --- a/rand_distr/src/weibull.rs +++ b/rand_distr/src/weibull.rs @@ -63,8 +63,7 @@ where OpenClosed01: Distribution #[cfg(test)] mod tests { - use crate::Distribution; - use super::Weibull; + use super::*; #[test] #[should_panic] @@ -83,4 +82,24 @@ mod tests { assert!(r >= 0.); } } + + #[test] + fn value_stability() { + fn test_samples> + (distr: D, zero: N, expected: &[N]) + { + 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(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]); + } } From f39915a4964052b1b091ab67d8cff137a848c633 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 15 Sep 2019 17:43:48 +0100 Subject: [PATCH 10/11] rand_distr: work around tan func issue --- rand_distr/src/cauchy.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rand_distr/src/cauchy.rs b/rand_distr/src/cauchy.rs index 8932a8e7787..520b607d6fc 100644 --- a/rand_distr/src/cauchy.rs +++ b/rand_distr/src/cauchy.rs @@ -130,8 +130,11 @@ mod test { assert_eq!(buf, expected); } + // Warning: in a few cases, results vary slightly between different + // platforms, presumably due to differences in precision of system impls + // of the tan function. We work around this by avoiding these values. test_samples(100f64, 10.0, &[77.93369152808678, 90.1606912098641, 125.31516221323625, 86.10217834773925]); - test_samples(20f32, 10.0, &[27.175842, -2.0663052, 11.013268, 10.160688]); + test_samples(10f32, 7.0, &[15.023088, -5.446413, 3.7092876, 3.112482]); } } From 5ac4cbd38c82797dde744edfb1fb0799dfdd8022 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 16 Sep 2019 20:41:46 +0100 Subject: [PATCH 11/11] Prepare rand 0.7.2 --- CHANGELOG.md | 7 +++++++ Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0550f47586b..e2b0f13b1ce 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. +## [0.7.2] - 2019-09-16 +### Fixes +- Fix dependency on `rand_core` 0.5.1 (#890) + +### Additions +- Unit tests for value stability of distributions added (#888) + ## [0.7.1] - 2019-09-13 ### Fixes - Fix `no_std` behaviour, appropriately enable c2-chacha's `std` feature (#844) diff --git a/Cargo.toml b/Cargo.toml index 2611ecb0d13..c1a0fe32a47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rand" -version = "0.7.1" +version = "0.7.2" authors = ["The Rand Project Developers", "The Rust Project Developers"] license = "MIT OR Apache-2.0" readme = "README.md" @@ -56,7 +56,7 @@ members = [ ] [dependencies] -rand_core = { path = "rand_core", version = "0.5" } +rand_core = { path = "rand_core", version = "0.5.1" } rand_pcg = { path = "rand_pcg", version = "0.2", optional = true } # Do not depend on 'getrandom_package' directly; use the 'getrandom' feature! # This is a dependency because: we forward wasm feature flags