From 30aaa4536816f77874b5129c2cfc40540d68b2dc Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 5 Sep 2024 13:47:30 +0200 Subject: [PATCH 1/3] refactor!: decouple distribution traits from rand This changes the behavior of Cauchy::mean() and Cauchy::variance() from the old default implementation (based on RNG sampling) to simply returning `None`. Both mean and variance are undefined for the cauchy distribution. This also changes the trait signature of both Distribution and DiscreteDistribution to not be subtraits of rand::distribution::Distribution. Our distributions do still implement this trait iff the `rand` feature is enabled. --- src/statistics/traits.rs | 42 ++++------------------------------------ 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/src/statistics/traits.rs b/src/statistics/traits.rs index e5211075..9140eab4 100644 --- a/src/statistics/traits.rs +++ b/src/statistics/traits.rs @@ -1,7 +1,5 @@ use ::num_traits::float::Float; -const STEPS: usize = 1_000; - /// The `Min` trait specifies than an object has a minimum value pub trait Min { /// Returns the minimum value in the domain of a given distribution @@ -35,7 +33,7 @@ pub trait Max { /// ``` fn max(&self) -> T; } -pub trait DiscreteDistribution: ::rand::distributions::Distribution { +pub trait DiscreteDistribution { /// Returns the mean, if it exists. fn mean(&self) -> Option { None @@ -58,14 +56,8 @@ pub trait DiscreteDistribution: ::rand::distributions::Distribution: ::rand::distributions::Distribution { +pub trait Distribution { /// Returns the mean, if it exists. - /// The default implementation returns an estimation - /// based on random samples. This is a crude estimate - /// for when no further information is known about the - /// distribution. More accurate statements about the - /// mean can and should be given by overriding the - /// default implementation. /// /// # Examples /// @@ -77,23 +69,9 @@ pub trait Distribution: ::rand::distributions::Distribution { /// assert_eq!(0.5, n.mean().unwrap()); /// ``` fn mean(&self) -> Option { - // TODO: Does not need cryptographic rng - let mut rng = ::rand::rngs::OsRng; - let mut mean = T::zero(); - let mut steps = T::zero(); - for _ in 0..STEPS { - steps = steps + T::one(); - mean = mean + Self::sample(self, &mut rng); - } - Some(mean / steps) + None } /// Returns the variance, if it exists. - /// The default implementation returns an estimation - /// based on random samples. This is a crude estimate - /// for when no further information is known about the - /// distribution. More accurate statements about the - /// variance can and should be given by overriding the - /// default implementation. /// /// # Examples /// @@ -105,19 +83,7 @@ pub trait Distribution: ::rand::distributions::Distribution { /// assert_eq!(1.0 / 12.0, n.variance().unwrap()); /// ``` fn variance(&self) -> Option { - // TODO: Does not need cryptographic rng - let mut rng = ::rand::rngs::OsRng; - let mut mean = T::zero(); - let mut variance = T::zero(); - let mut steps = T::zero(); - for _ in 0..STEPS { - steps = steps + T::one(); - let sample = Self::sample(self, &mut rng); - variance = variance + (steps - T::one()) * (sample - mean) * (sample - mean) / steps; - mean = mean + (sample - mean) / steps; - } - steps = steps - T::one(); - Some(variance / steps) + None } /// Returns the standard deviation, if it exists. /// From da19f85588153eae3fef07dd16a920fdeee33a88 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 5 Sep 2024 14:17:49 +0200 Subject: [PATCH 2/3] build: make `rand` an optional dependency --- Cargo.toml | 11 ++++++++--- benches/order_statistics.rs | 1 - src/distribution/bernoulli.rs | 4 ++-- src/distribution/beta.rs | 4 ++-- src/distribution/binomial.rs | 4 ++-- src/distribution/categorical.rs | 7 ++++--- src/distribution/cauchy.rs | 4 ++-- src/distribution/chi.rs | 4 ++-- src/distribution/chi_squared.rs | 4 ++-- src/distribution/dirac.rs | 4 ++-- src/distribution/dirichlet.rs | 8 ++++---- src/distribution/discrete_uniform.rs | 4 ++-- src/distribution/empirical.rs | 8 +++++--- src/distribution/erlang.rs | 4 ++-- src/distribution/exponential.rs | 8 +++++--- src/distribution/fisher_snedecor.rs | 4 ++-- src/distribution/gamma.rs | 7 ++++--- src/distribution/geometric.rs | 7 ++++--- src/distribution/hypergeometric.rs | 4 ++-- src/distribution/inverse_gamma.rs | 4 ++-- src/distribution/laplace.rs | 11 +++++++---- src/distribution/log_normal.rs | 4 ++-- src/distribution/mod.rs | 2 ++ src/distribution/multinomial.rs | 8 +++++--- src/distribution/multivariate_normal.rs | 7 +++---- src/distribution/multivariate_students_t.rs | 7 ++++--- src/distribution/negative_binomial.rs | 10 ++++++---- src/distribution/normal.rs | 11 +++++++---- src/distribution/pareto.rs | 7 ++++--- src/distribution/poisson.rs | 7 ++++--- src/distribution/students_t.rs | 4 ++-- src/distribution/triangular.rs | 7 ++++--- src/distribution/uniform.rs | 8 ++++---- src/distribution/weibull.rs | 4 ++-- src/statistics/slice_statistics.rs | 4 +++- 35 files changed, 117 insertions(+), 89 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f9f531df..6c80cc70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,21 +22,26 @@ path = "src/lib.rs" [[bench]] name = "order_statistics" harness = false +required-features = ["rand"] [features] -default = ["nalgebra"] +default = ["nalgebra", "rand"] nalgebra = ["dep:nalgebra"] +rand = ["dep:rand", "nalgebra?/rand"] [dependencies] -rand = "0.8" approx = "0.5.0" num-traits = "0.2.14" +[dependencies.rand] +version = "0.8" +optional = true + [dependencies.nalgebra] version = "0.32" optional = true default-features = false -features = ["rand", "std"] +features = ["std"] [dev-dependencies] criterion = "0.5" diff --git a/benches/order_statistics.rs b/benches/order_statistics.rs index fa6fdd26..d94902c9 100644 --- a/benches/order_statistics.rs +++ b/benches/order_statistics.rs @@ -1,4 +1,3 @@ -extern crate rand; extern crate statrs; use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use rand::prelude::*; diff --git a/src/distribution/bernoulli.rs b/src/distribution/bernoulli.rs index d5de981a..28f9d104 100644 --- a/src/distribution/bernoulli.rs +++ b/src/distribution/bernoulli.rs @@ -1,6 +1,5 @@ use crate::distribution::{Binomial, BinomialError, Discrete, DiscreteCDF}; use crate::statistics::*; -use rand::Rng; /// Implements the /// [Bernoulli](https://en.wikipedia.org/wiki/Bernoulli_distribution) @@ -85,8 +84,9 @@ impl std::fmt::Display for Bernoulli { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Bernoulli { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { rng.gen_bool(self.p()) as u8 as f64 } } diff --git a/src/distribution/beta.rs b/src/distribution/beta.rs index e20ea302..2741889e 100644 --- a/src/distribution/beta.rs +++ b/src/distribution/beta.rs @@ -1,7 +1,6 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::{beta, gamma}; use crate::statistics::*; -use rand::Rng; /// Implements the [Beta](https://en.wikipedia.org/wiki/Beta_distribution) /// distribution @@ -121,8 +120,9 @@ impl std::fmt::Display for Beta { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Beta { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { // Generated by sampling two gamma distributions and normalizing. let x = super::gamma::sample_unchecked(rng, self.shape_a, 1.0); let y = super::gamma::sample_unchecked(rng, self.shape_b, 1.0); diff --git a/src/distribution/binomial.rs b/src/distribution/binomial.rs index 1d86283d..c24bf7b5 100644 --- a/src/distribution/binomial.rs +++ b/src/distribution/binomial.rs @@ -1,7 +1,6 @@ use crate::distribution::{Discrete, DiscreteCDF}; use crate::function::{beta, factorial}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the @@ -110,8 +109,9 @@ impl std::fmt::Display for Binomial { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Binomial { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { (0..self.n).fold(0.0, |acc, _| { let n: f64 = rng.gen(); if n < self.p { diff --git a/src/distribution/categorical.rs b/src/distribution/categorical.rs index cb3c7ea8..7d3a7c1c 100644 --- a/src/distribution/categorical.rs +++ b/src/distribution/categorical.rs @@ -1,6 +1,5 @@ use crate::distribution::{Discrete, DiscreteCDF}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the @@ -124,8 +123,9 @@ impl std::fmt::Display for Categorical { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Categorical { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { sample_unchecked(rng, &self.cdf) } } @@ -322,7 +322,8 @@ impl Discrete for Categorical { /// Draws a sample from the categorical distribution described by `cdf` /// without doing any bounds checking -pub fn sample_unchecked(rng: &mut R, cdf: &[f64]) -> f64 { +#[cfg(feature = "rand")] +pub fn sample_unchecked(rng: &mut R, cdf: &[f64]) -> f64 { let draw = rng.gen::() * cdf.last().unwrap(); cdf.iter() .enumerate() diff --git a/src/distribution/cauchy.rs b/src/distribution/cauchy.rs index 5ba7f69f..c9fc4ae2 100644 --- a/src/distribution/cauchy.rs +++ b/src/distribution/cauchy.rs @@ -1,6 +1,5 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the [Cauchy](https://en.wikipedia.org/wiki/Cauchy_distribution) @@ -111,8 +110,9 @@ impl std::fmt::Display for Cauchy { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Cauchy { - fn sample(&self, r: &mut R) -> f64 { + fn sample(&self, r: &mut R) -> f64 { self.location + self.scale * (f64::consts::PI * (r.gen::() - 0.5)).tan() } } diff --git a/src/distribution/chi.rs b/src/distribution/chi.rs index 796fcd23..bcb98481 100644 --- a/src/distribution/chi.rs +++ b/src/distribution/chi.rs @@ -1,7 +1,6 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::gamma; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the [Chi](https://en.wikipedia.org/wiki/Chi_distribution) @@ -94,8 +93,9 @@ impl std::fmt::Display for Chi { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Chi { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { (0..self.freedom as i64) .fold(0.0, |acc, _| { acc + super::normal::sample_unchecked(rng, 0.0, 1.0).powf(2.0) diff --git a/src/distribution/chi_squared.rs b/src/distribution/chi_squared.rs index a847ac94..f61d6e19 100644 --- a/src/distribution/chi_squared.rs +++ b/src/distribution/chi_squared.rs @@ -1,6 +1,5 @@ use crate::distribution::{Continuous, ContinuousCDF, Gamma, GammaError}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the @@ -101,8 +100,9 @@ impl std::fmt::Display for ChiSquared { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for ChiSquared { - fn sample(&self, r: &mut R) -> f64 { + fn sample(&self, r: &mut R) -> f64 { ::rand::distributions::Distribution::sample(&self.g, r) } } diff --git a/src/distribution/dirac.rs b/src/distribution/dirac.rs index 18e70f9b..ec833d93 100644 --- a/src/distribution/dirac.rs +++ b/src/distribution/dirac.rs @@ -1,6 +1,5 @@ use crate::distribution::ContinuousCDF; use crate::statistics::*; -use rand::Rng; /// Implements the [Dirac Delta](https://en.wikipedia.org/wiki/Dirac_delta_function#As_a_distribution) /// distribution @@ -69,8 +68,9 @@ impl std::fmt::Display for Dirac { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Dirac { - fn sample(&self, _: &mut R) -> f64 { + fn sample(&self, _: &mut R) -> f64 { self.0 } } diff --git a/src/distribution/dirichlet.rs b/src/distribution/dirichlet.rs index 355476db..7c7c9913 100644 --- a/src/distribution/dirichlet.rs +++ b/src/distribution/dirichlet.rs @@ -2,8 +2,7 @@ use crate::distribution::Continuous; use crate::function::gamma; use crate::prec; use crate::statistics::*; -use nalgebra::{Const, Dim, Dyn, OMatrix, OVector}; -use rand::Rng; +use nalgebra::{Dim, Dyn, OMatrix, OVector}; use std::f64; /// Implements the @@ -192,16 +191,17 @@ where } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution> for Dirichlet where D: Dim, nalgebra::DefaultAllocator: nalgebra::allocator::Allocator, { - fn sample(&self, rng: &mut R) -> OVector { + fn sample(&self, rng: &mut R) -> OVector { let mut sum = 0.0; OVector::from_iterator_generic( self.alpha.shape_generic().0, - Const::<1>, + nalgebra::Const::<1>, self.alpha.iter().map(|&a| { let sample = super::gamma::sample_unchecked(rng, a, 1.0); sum += sample; diff --git a/src/distribution/discrete_uniform.rs b/src/distribution/discrete_uniform.rs index 524bc2a3..85b26090 100644 --- a/src/distribution/discrete_uniform.rs +++ b/src/distribution/discrete_uniform.rs @@ -1,6 +1,5 @@ use crate::distribution::{Discrete, DiscreteCDF}; use crate::statistics::*; -use rand::Rng; /// Implements the [Discrete /// Uniform](https://en.wikipedia.org/wiki/Discrete_uniform_distribution) @@ -75,8 +74,9 @@ impl std::fmt::Display for DiscreteUniform { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for DiscreteUniform { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { rng.gen_range(self.min..=self.max) as f64 } } diff --git a/src/distribution/empirical.rs b/src/distribution/empirical.rs index 6dc7ec71..965d8c7f 100644 --- a/src/distribution/empirical.rs +++ b/src/distribution/empirical.rs @@ -1,7 +1,6 @@ -use crate::distribution::{ContinuousCDF, Uniform}; +use crate::distribution::ContinuousCDF; use crate::statistics::*; use core::cmp::Ordering; -use rand::Rng; use std::collections::BTreeMap; #[derive(Clone, PartialEq, Debug)] @@ -176,8 +175,11 @@ impl std::fmt::Display for Empirical { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Empirical { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { + use crate::distribution::Uniform; + let uniform = Uniform::new(0.0, 1.0).unwrap(); self.__inverse_cdf(uniform.sample(rng)) } diff --git a/src/distribution/erlang.rs b/src/distribution/erlang.rs index 9b7a332c..2ad017f3 100644 --- a/src/distribution/erlang.rs +++ b/src/distribution/erlang.rs @@ -1,6 +1,5 @@ use crate::distribution::{Continuous, ContinuousCDF, Gamma, GammaError}; use crate::statistics::*; -use rand::Rng; /// Implements the [Erlang](https://en.wikipedia.org/wiki/Erlang_distribution) /// distribution @@ -83,8 +82,9 @@ impl std::fmt::Display for Erlang { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Erlang { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { ::rand::distributions::Distribution::sample(&self.g, rng) } } diff --git a/src/distribution/exponential.rs b/src/distribution/exponential.rs index ec30d1f7..9c6c21fc 100644 --- a/src/distribution/exponential.rs +++ b/src/distribution/exponential.rs @@ -1,6 +1,5 @@ -use crate::distribution::{ziggurat, Continuous, ContinuousCDF}; +use crate::distribution::{Continuous, ContinuousCDF}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the @@ -91,8 +90,11 @@ impl std::fmt::Display for Exp { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Exp { - fn sample(&self, r: &mut R) -> f64 { + fn sample(&self, r: &mut R) -> f64 { + use crate::distribution::ziggurat; + ziggurat::sample_exp_1(r) / self.rate } } diff --git a/src/distribution/fisher_snedecor.rs b/src/distribution/fisher_snedecor.rs index 610da130..3208f98f 100644 --- a/src/distribution/fisher_snedecor.rs +++ b/src/distribution/fisher_snedecor.rs @@ -1,7 +1,6 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::beta; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the @@ -124,8 +123,9 @@ impl std::fmt::Display for FisherSnedecor { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for FisherSnedecor { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { (super::gamma::sample_unchecked(rng, self.freedom_1 / 2.0, 0.5) * self.freedom_2) / (super::gamma::sample_unchecked(rng, self.freedom_2 / 2.0, 0.5) * self.freedom_1) } diff --git a/src/distribution/gamma.rs b/src/distribution/gamma.rs index b6055c77..89341439 100644 --- a/src/distribution/gamma.rs +++ b/src/distribution/gamma.rs @@ -2,7 +2,6 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::gamma; use crate::prec; use crate::statistics::*; -use rand::Rng; /// Implements the [Gamma](https://en.wikipedia.org/wiki/Gamma_distribution) /// distribution @@ -122,8 +121,9 @@ impl std::fmt::Display for Gamma { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Gamma { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { sample_unchecked(rng, self.shape, self.rate) } } @@ -400,7 +400,8 @@ impl Continuous for Gamma { /// /// ACM Transactions on Mathematical Software, Vol. 26, No. 3, September 2000, /// Pages 363-372 -pub fn sample_unchecked(rng: &mut R, shape: f64, rate: f64) -> f64 { +#[cfg(feature = "rand")] +pub fn sample_unchecked(rng: &mut R, shape: f64, rate: f64) -> f64 { let mut a = shape; let mut afix = 1.0; if shape < 1.0 { diff --git a/src/distribution/geometric.rs b/src/distribution/geometric.rs index 41e35f52..82af5eef 100644 --- a/src/distribution/geometric.rs +++ b/src/distribution/geometric.rs @@ -1,7 +1,5 @@ use crate::distribution::{Discrete, DiscreteCDF}; use crate::statistics::*; -use rand::distributions::OpenClosed01; -use rand::Rng; use std::f64; /// Implements the @@ -92,8 +90,11 @@ impl std::fmt::Display for Geometric { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Geometric { - fn sample(&self, r: &mut R) -> f64 { + fn sample(&self, r: &mut R) -> f64 { + use ::rand::distributions::OpenClosed01; + if ulps_eq!(self.p, 1.0) { 1.0 } else { diff --git a/src/distribution/hypergeometric.rs b/src/distribution/hypergeometric.rs index 7da6f45a..ac39917d 100644 --- a/src/distribution/hypergeometric.rs +++ b/src/distribution/hypergeometric.rs @@ -1,7 +1,6 @@ use crate::distribution::{Discrete, DiscreteCDF}; use crate::function::factorial; use crate::statistics::*; -use rand::Rng; use std::cmp; use std::f64; @@ -146,8 +145,9 @@ impl std::fmt::Display for Hypergeometric { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Hypergeometric { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { let mut population = self.population as f64; let mut successes = self.successes as f64; let mut draws = self.draws; diff --git a/src/distribution/inverse_gamma.rs b/src/distribution/inverse_gamma.rs index a36c7e17..db101fd0 100644 --- a/src/distribution/inverse_gamma.rs +++ b/src/distribution/inverse_gamma.rs @@ -1,7 +1,6 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::gamma; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the [Inverse @@ -119,8 +118,9 @@ impl std::fmt::Display for InverseGamma { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for InverseGamma { - fn sample(&self, r: &mut R) -> f64 { + fn sample(&self, r: &mut R) -> f64 { 1.0 / super::gamma::sample_unchecked(r, self.shape, self.rate) } } diff --git a/src/distribution/laplace.rs b/src/distribution/laplace.rs index 13f03a55..b54bbd9f 100644 --- a/src/distribution/laplace.rs +++ b/src/distribution/laplace.rs @@ -1,6 +1,5 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::statistics::{Distribution, Max, Median, Min, Mode}; -use rand::Rng; use std::f64; /// Implements the [Laplace](https://en.wikipedia.org/wiki/Laplace_distribution) @@ -111,8 +110,9 @@ impl std::fmt::Display for Laplace { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Laplace { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { let x: f64 = rng.gen_range(-0.5..0.5); self.location - self.scale * x.signum() * (1. - 2. * x.abs()).ln() } @@ -326,7 +326,6 @@ impl Continuous for Laplace { #[cfg(test)] mod tests { use super::*; - use rand::thread_rng; use crate::testing_boiler; @@ -551,18 +550,22 @@ mod tests { test_rel_close(loc, scale, expected, reltol, inverse_cdf(0.95)); } + #[cfg(feature = "rand")] #[test] fn test_sample() { use ::rand::distributions::Distribution; + use ::rand::thread_rng; + let l = create_ok(0.1, 0.5); l.sample(&mut thread_rng()); } + #[cfg(feature = "rand")] #[test] fn test_sample_distribution() { + use ::rand::distributions::Distribution; use ::rand::rngs::StdRng; use ::rand::SeedableRng; - use rand::distributions::Distribution; // sanity check sampling let location = 0.0; diff --git a/src/distribution/log_normal.rs b/src/distribution/log_normal.rs index 49380d2b..2cf9d7cb 100644 --- a/src/distribution/log_normal.rs +++ b/src/distribution/log_normal.rs @@ -2,7 +2,6 @@ use crate::consts; use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::erf; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the @@ -88,8 +87,9 @@ impl std::fmt::Display for LogNormal { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for LogNormal { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { super::normal::sample_unchecked(rng, self.location, self.scale).exp() } } diff --git a/src/distribution/mod.rs b/src/distribution/mod.rs index 8955ed63..e8c9574c 100644 --- a/src/distribution/mod.rs +++ b/src/distribution/mod.rs @@ -78,7 +78,9 @@ mod students_t; mod triangular; mod uniform; mod weibull; +#[cfg(feature = "rand")] mod ziggurat; +#[cfg(feature = "rand")] mod ziggurat_tables; /// The `ContinuousCDF` trait is used to specify an interface for univariate diff --git a/src/distribution/multinomial.rs b/src/distribution/multinomial.rs index 7d1b408c..d6304214 100644 --- a/src/distribution/multinomial.rs +++ b/src/distribution/multinomial.rs @@ -1,8 +1,7 @@ use crate::distribution::Discrete; use crate::function::factorial; use crate::statistics::*; -use nalgebra::{Const, DVector, Dim, Dyn, OMatrix, OVector}; -use rand::Rng; +use nalgebra::{DVector, Dim, Dyn, OMatrix, OVector}; /// Implements the /// [Multinomial](https://en.wikipedia.org/wiki/Multinomial_distribution) @@ -160,12 +159,15 @@ where } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution> for Multinomial where D: Dim, nalgebra::DefaultAllocator: nalgebra::allocator::Allocator, { - fn sample(&self, rng: &mut R) -> OVector { + fn sample(&self, rng: &mut R) -> OVector { + use nalgebra::Const; + let p_cdf = super::categorical::prob_mass_to_cdf(self.p().as_slice()); let mut res = OVector::zeros_generic(self.p.shape_generic().0, Const::<1>); for _ in 0..self.n { diff --git a/src/distribution/multivariate_normal.rs b/src/distribution/multivariate_normal.rs index eb86edd3..b336d9db 100644 --- a/src/distribution/multivariate_normal.rs +++ b/src/distribution/multivariate_normal.rs @@ -1,9 +1,7 @@ use crate::distribution::Continuous; -use crate::distribution::Normal; use crate::statistics::{Max, MeanN, Min, Mode, VarianceN}; use crate::StatsError; use nalgebra::{Cholesky, Const, DMatrix, DVector, Dim, DimMin, Dyn, OMatrix, OVector}; -use rand::Rng; use std::f64; use std::f64::consts::{E, PI}; @@ -247,6 +245,7 @@ where } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution> for MultivariateNormal where D: Dim, @@ -264,8 +263,8 @@ where /// `Z` is a vector of normally distributed random variables, and /// `μ` is the mean vector - fn sample(&self, rng: &mut R) -> OVector { - let d = Normal::new(0., 1.).unwrap(); + fn sample(&self, rng: &mut R) -> OVector { + let d = crate::distribution::Normal::new(0., 1.).unwrap(); let z = OVector::from_distribution_generic(self.mu.shape_generic().0, Const::<1>, &d, rng); (&self.cov_chol_decomp * z) + &self.mu } diff --git a/src/distribution/multivariate_students_t.rs b/src/distribution/multivariate_students_t.rs index b75e0a88..73e8f8f2 100644 --- a/src/distribution/multivariate_students_t.rs +++ b/src/distribution/multivariate_students_t.rs @@ -1,9 +1,7 @@ use crate::distribution::Continuous; -use crate::distribution::{ChiSquared, Normal}; use crate::function::gamma; use crate::statistics::{Max, MeanN, Min, Mode, VarianceN}; use nalgebra::{Cholesky, Const, DMatrix, Dim, DimMin, Dyn, OMatrix, OVector}; -use rand::Rng; use std::f64::consts::PI; /// Implements the [Multivariate Student's t-distribution](https://en.wikipedia.org/wiki/Multivariate_t-distribution) @@ -198,6 +196,7 @@ where } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution> for MultivariateStudent where D: Dim, @@ -217,7 +216,9 @@ where /// `L` is the Cholesky decomposition of the scale matrix, /// `Z` is a vector of normally distributed random variables, and /// `μ` is the location vector - fn sample(&self, rng: &mut R) -> OVector { + fn sample(&self, rng: &mut R) -> OVector { + use crate::distribution::{ChiSquared, Normal}; + let d = Normal::new(0., 1.).unwrap(); let s = ChiSquared::new(self.freedom).unwrap(); let w = (self.freedom / s.sample(rng)).sqrt(); diff --git a/src/distribution/negative_binomial.rs b/src/distribution/negative_binomial.rs index 6ed557be..29e22eee 100644 --- a/src/distribution/negative_binomial.rs +++ b/src/distribution/negative_binomial.rs @@ -1,7 +1,6 @@ -use crate::distribution::{self, poisson, Discrete, DiscreteCDF}; +use crate::distribution::{Discrete, DiscreteCDF}; use crate::function::{beta, gamma}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the @@ -136,9 +135,12 @@ impl std::fmt::Display for NegativeBinomial { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for NegativeBinomial { - fn sample(&self, r: &mut R) -> u64 { - let lambda = distribution::gamma::sample_unchecked(r, self.r, (1.0 - self.p) / self.p); + fn sample(&self, r: &mut R) -> u64 { + use crate::distribution::{gamma, poisson}; + + let lambda = gamma::sample_unchecked(r, self.r, (1.0 - self.p) / self.p); poisson::sample_unchecked(r, lambda).floor() as u64 } } diff --git a/src/distribution/normal.rs b/src/distribution/normal.rs index b536c101..a264af50 100644 --- a/src/distribution/normal.rs +++ b/src/distribution/normal.rs @@ -1,8 +1,7 @@ use crate::consts; -use crate::distribution::{ziggurat, Continuous, ContinuousCDF}; +use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::erf; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the [Normal](https://en.wikipedia.org/wiki/Normal_distribution) @@ -106,8 +105,9 @@ impl std::fmt::Display for Normal { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Normal { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { sample_unchecked(rng, self.mean, self.std_dev) } } @@ -347,8 +347,11 @@ pub fn ln_pdf_unchecked(x: f64, mean: f64, std_dev: f64) -> f64 { (-0.5 * d * d) - consts::LN_SQRT_2PI - std_dev.ln() } +#[cfg(feature = "rand")] /// draws a sample from a normal distribution using the Box-Muller algorithm -pub fn sample_unchecked(rng: &mut R, mean: f64, std_dev: f64) -> f64 { +pub fn sample_unchecked(rng: &mut R, mean: f64, std_dev: f64) -> f64 { + use crate::distribution::ziggurat; + mean + std_dev * ziggurat::sample_std_normal(rng) } diff --git a/src/distribution/pareto.rs b/src/distribution/pareto.rs index 886db43b..1de73d84 100644 --- a/src/distribution/pareto.rs +++ b/src/distribution/pareto.rs @@ -1,7 +1,5 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::statistics::*; -use rand::distributions::OpenClosed01; -use rand::Rng; use std::f64; /// Implements the [Pareto](https://en.wikipedia.org/wiki/Pareto_distribution) @@ -114,8 +112,11 @@ impl std::fmt::Display for Pareto { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Pareto { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { + use rand::distributions::OpenClosed01; + // Inverse transform sampling let u: f64 = rng.sample(OpenClosed01); self.scale * u.powf(-1.0 / self.shape) diff --git a/src/distribution/poisson.rs b/src/distribution/poisson.rs index 33e8f8a2..b3f1ebab 100644 --- a/src/distribution/poisson.rs +++ b/src/distribution/poisson.rs @@ -1,7 +1,6 @@ use crate::distribution::{Discrete, DiscreteCDF}; use crate::function::{factorial, gamma}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the [Poisson](https://en.wikipedia.org/wiki/Poisson_distribution) @@ -90,13 +89,14 @@ impl std::fmt::Display for Poisson { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Poisson { /// Generates one sample from the Poisson distribution either by /// Knuth's method if lambda < 30.0 or Rejection method PA by /// A. C. Atkinson from the Journal of the Royal Statistical Society /// Series C (Applied Statistics) Vol. 28 No. 1. (1979) pp. 29 - 35 /// otherwise - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { sample_unchecked(rng, self.lambda) } } @@ -283,7 +283,8 @@ impl Discrete for Poisson { /// A. C. Atkinson from the Journal of the Royal Statistical Society /// Series C (Applied Statistics) Vol. 28 No. 1. (1979) pp. 29 - 35 /// otherwise -pub fn sample_unchecked(rng: &mut R, lambda: f64) -> f64 { +#[cfg(feature = "rand")] +pub fn sample_unchecked(rng: &mut R, lambda: f64) -> f64 { if lambda < 30.0 { let limit = (-lambda).exp(); let mut count = 0.0; diff --git a/src/distribution/students_t.rs b/src/distribution/students_t.rs index cc88707f..117fbc87 100644 --- a/src/distribution/students_t.rs +++ b/src/distribution/students_t.rs @@ -1,7 +1,6 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::{beta, gamma}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the [Student's @@ -143,8 +142,9 @@ impl std::fmt::Display for StudentsT { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for StudentsT { - fn sample(&self, r: &mut R) -> f64 { + fn sample(&self, r: &mut R) -> f64 { // based on method 2, section 5 in chapter 9 of L. Devroye's // "Non-Uniform Random Variate Generation" let gamma = super::gamma::sample_unchecked(r, 0.5 * self.freedom, 0.5); diff --git a/src/distribution/triangular.rs b/src/distribution/triangular.rs index eb3cb93d..1a83be2c 100644 --- a/src/distribution/triangular.rs +++ b/src/distribution/triangular.rs @@ -1,6 +1,5 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the @@ -112,8 +111,9 @@ impl std::fmt::Display for Triangular { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Triangular { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { sample_unchecked(rng, self.min, self.max, self.mode) } } @@ -382,7 +382,8 @@ impl Continuous for Triangular { } } -fn sample_unchecked(rng: &mut R, min: f64, max: f64, mode: f64) -> f64 { +#[cfg(feature = "rand")] +fn sample_unchecked(rng: &mut R, min: f64, max: f64, mode: f64) -> f64 { let f: f64 = rng.gen(); if f < (mode - min) / (max - min) { min + (f * (max - min) * (mode - min)).sqrt() diff --git a/src/distribution/uniform.rs b/src/distribution/uniform.rs index 55bd7884..3d637734 100644 --- a/src/distribution/uniform.rs +++ b/src/distribution/uniform.rs @@ -1,7 +1,5 @@ use crate::distribution::{Continuous, ContinuousCDF}; use crate::statistics::*; -use rand::distributions::Uniform as RandUniform; -use rand::Rng; use std::f64; use std::fmt::Debug; @@ -121,9 +119,10 @@ impl std::fmt::Display for Uniform { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Uniform { - fn sample(&self, rng: &mut R) -> f64 { - let d = RandUniform::new_inclusive(self.min, self.max); + fn sample(&self, rng: &mut R) -> f64 { + let d = rand::distributions::Uniform::new_inclusive(self.min, self.max); rng.sample(d) } } @@ -495,6 +494,7 @@ mod tests { test::check_continuous_distribution(&create_ok(-2.0, 15.0), -2.0, 15.0); } + #[cfg(feature = "rand")] #[test] fn test_samples_in_range() { use rand::rngs::StdRng; diff --git a/src/distribution/weibull.rs b/src/distribution/weibull.rs index 71aa30ef..aacf7bb6 100644 --- a/src/distribution/weibull.rs +++ b/src/distribution/weibull.rs @@ -2,7 +2,6 @@ use crate::consts; use crate::distribution::{Continuous, ContinuousCDF}; use crate::function::gamma; use crate::statistics::*; -use rand::Rng; use std::f64; /// Implements the [Weibull](https://en.wikipedia.org/wiki/Weibull_distribution) @@ -121,8 +120,9 @@ impl std::fmt::Display for Weibull { } } +#[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Weibull { - fn sample(&self, rng: &mut R) -> f64 { + fn sample(&self, rng: &mut R) -> f64 { let x: f64 = rng.gen(); self.scale * (-x.ln()).powf(1.0 / self.shape) } diff --git a/src/statistics/slice_statistics.rs b/src/statistics/slice_statistics.rs index ea2f3096..b0b1d2a7 100644 --- a/src/statistics/slice_statistics.rs +++ b/src/statistics/slice_statistics.rs @@ -1,6 +1,5 @@ use crate::statistics::*; use core::ops::{Index, IndexMut}; -use rand::prelude::SliceRandom; #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Data(D); @@ -133,8 +132,11 @@ impl + AsRef<[f64]>> Data { } } +#[cfg(feature = "rand")] impl> ::rand::distributions::Distribution for Data { fn sample(&self, rng: &mut R) -> f64 { + use rand::prelude::SliceRandom; + *self.0.as_ref().choose(rng).unwrap() } } From 35c5dd04f6452680550aec301b803b0d98b03822 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 5 Sep 2024 14:30:05 +0200 Subject: [PATCH 3/3] ci: check all feature combinations --- .github/workflows/test.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 88b02134..65d901eb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,12 +20,9 @@ jobs: with: components: clippy - - name: Run cargo clippy + - name: Run cargo clippy (default features) run: cargo clippy --all-targets - - name: Run cargo clippy without default features - run: cargo clippy --no-default-features --all-targets - fmt: runs-on: ubuntu-latest steps: @@ -65,3 +62,15 @@ jobs: - name: Test default features run: cargo test + features: + needs: [clippy, fmt] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v2 + - name: Check all possible feature sets + run: cargo hack check --feature-powerset --no-dev-deps