From 8eff02ab6cb906a8f71ff185e3ac6b60b17192c1 Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Sun, 5 Sep 2021 21:06:52 +0200 Subject: [PATCH 1/9] 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 317168e9..d2afc9d9 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 61c2aa18..86b66a97 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 46860e31..b1b73555 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 00000000..de4f5ddc --- /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 b1d1aa28..21285f10 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 2/9] 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 04d50238..9b919f39 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 3/9] 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 de4f5ddc..7b2eaf0f 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 3f331624006245f63497cdb4c134363453434993 Mon Sep 17 00:00:00 2001 From: raimundo saona Date: Thu, 9 Sep 2021 20:36:56 +0200 Subject: [PATCH 4/9] 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 7b2eaf0f..99476f94 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 5/9] 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 99476f94..63c286e3 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 6/9] 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 d2afc9d9..680c8696 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 21285f10..198b690d 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 7/9] 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 63c286e3..efcdbcbc 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 85c9bc57bff1a5329cf45dddf295bc7da65bb41e Mon Sep 17 00:00:00 2001 From: Vinzent Steinberg Date: Fri, 10 Sep 2021 15:23:11 +0200 Subject: [PATCH 8/9] 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 19cf619a..fe0b3ff6 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 9/9] 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 c792c3a9..28ff439e 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