Skip to content

Commit b9cc581

Browse files
authored
Merge pull request #548 from sicking/mods
Hide modules that don't need to be publicly exported
2 parents 515cf18 + dd37c58 commit b9cc581

File tree

8 files changed

+146
-164
lines changed

8 files changed

+146
-164
lines changed

src/distributions/binomial.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
1313
use Rng;
1414
use distributions::{Distribution, Bernoulli, Cauchy};
15-
use distributions::log_gamma::log_gamma;
15+
use distributions::utils::log_gamma;
1616

1717
/// The binomial distribution `Binomial(n, p)`.
1818
///

src/distributions/exponential.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
//! The exponential distribution.
1212
1313
use {Rng};
14-
use distributions::{ziggurat, ziggurat_tables, Distribution};
14+
use distributions::{ziggurat_tables, Distribution};
15+
use distributions::utils::ziggurat;
1516

1617
/// Samples floating-point numbers according to the exponential distribution,
1718
/// with rate parameter `λ = 1`. This is equivalent to `Exp::new(1.0)` or

src/distributions/log_gamma.rs

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/distributions/mod.rs

Lines changed: 23 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -173,60 +173,37 @@
173173
174174
use Rng;
175175

176-
#[doc(inline)] pub use self::other::Alphanumeric;
176+
pub use self::other::Alphanumeric;
177177
#[doc(inline)] pub use self::uniform::Uniform;
178-
#[doc(inline)] pub use self::float::{OpenClosed01, Open01};
179-
#[cfg(feature="alloc")]
180-
#[doc(inline)] pub use self::weighted::{WeightedIndex, WeightedError};
181-
#[cfg(feature="std")]
182-
#[doc(inline)] pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
183-
#[cfg(feature="std")]
184-
#[doc(inline)] pub use self::normal::{Normal, LogNormal, StandardNormal};
185-
#[cfg(feature="std")]
186-
#[doc(inline)] pub use self::exponential::{Exp, Exp1};
187-
#[cfg(feature="std")]
188-
#[doc(inline)] pub use self::pareto::Pareto;
189-
#[cfg(feature = "std")]
190-
#[doc(inline)] pub use self::poisson::Poisson;
191-
#[cfg(feature = "std")]
192-
#[doc(inline)] pub use self::binomial::Binomial;
193-
#[doc(inline)] pub use self::bernoulli::Bernoulli;
194-
#[cfg(feature = "std")]
195-
#[doc(inline)] pub use self::cauchy::Cauchy;
196-
#[cfg(feature = "std")]
197-
#[doc(inline)] pub use self::dirichlet::Dirichlet;
178+
pub use self::float::{OpenClosed01, Open01};
179+
pub use self::bernoulli::Bernoulli;
180+
#[cfg(feature="alloc")] pub use self::weighted::{WeightedIndex, WeightedError};
181+
#[cfg(feature="std")] pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
182+
#[cfg(feature="std")] pub use self::normal::{Normal, LogNormal, StandardNormal};
183+
#[cfg(feature="std")] pub use self::exponential::{Exp, Exp1};
184+
#[cfg(feature="std")] pub use self::pareto::Pareto;
185+
#[cfg(feature="std")] pub use self::poisson::Poisson;
186+
#[cfg(feature="std")] pub use self::binomial::Binomial;
187+
#[cfg(feature="std")] pub use self::cauchy::Cauchy;
188+
#[cfg(feature="std")] pub use self::dirichlet::Dirichlet;
198189

199190
pub mod uniform;
200-
#[cfg(feature="alloc")]
201-
#[doc(hidden)] pub mod weighted;
202-
#[cfg(feature="std")]
203-
#[doc(hidden)] pub mod gamma;
204-
#[cfg(feature="std")]
205-
#[doc(hidden)] pub mod normal;
206-
#[cfg(feature="std")]
207-
#[doc(hidden)] pub mod exponential;
208-
#[cfg(feature="std")]
209-
#[doc(hidden)] pub mod pareto;
210-
#[cfg(feature = "std")]
211-
#[doc(hidden)] pub mod poisson;
212-
#[cfg(feature = "std")]
213-
#[doc(hidden)] pub mod binomial;
214-
#[doc(hidden)] pub mod bernoulli;
215-
#[cfg(feature = "std")]
216-
#[doc(hidden)] pub mod cauchy;
217-
#[cfg(feature = "std")]
218-
#[doc(hidden)] pub mod dirichlet;
191+
mod bernoulli;
192+
#[cfg(feature="alloc")] mod weighted;
193+
#[cfg(feature="std")] mod gamma;
194+
#[cfg(feature="std")] mod normal;
195+
#[cfg(feature="std")] mod exponential;
196+
#[cfg(feature="std")] mod pareto;
197+
#[cfg(feature="std")] mod poisson;
198+
#[cfg(feature="std")] mod binomial;
199+
#[cfg(feature="std")] mod cauchy;
200+
#[cfg(feature="std")] mod dirichlet;
219201

220202
mod float;
221203
mod integer;
222-
#[cfg(feature="std")]
223-
mod log_gamma;
224204
mod other;
225205
mod utils;
226-
#[cfg(feature="std")]
227-
mod ziggurat_tables;
228-
#[cfg(feature="std")]
229-
use distributions::float::IntoFloat;
206+
#[cfg(feature="std")] mod ziggurat_tables;
230207

231208
/// Types (distributions) that can be used to create a random instance of `T`.
232209
///
@@ -487,69 +464,6 @@ impl<'a, T: Clone> Distribution<T> for WeightedChoice<'a, T> {
487464
}
488465
}
489466

490-
/// Sample a random number using the Ziggurat method (specifically the
491-
/// ZIGNOR variant from Doornik 2005). Most of the arguments are
492-
/// directly from the paper:
493-
///
494-
/// * `rng`: source of randomness
495-
/// * `symmetric`: whether this is a symmetric distribution, or one-sided with P(x < 0) = 0.
496-
/// * `X`: the $x_i$ abscissae.
497-
/// * `F`: precomputed values of the PDF at the $x_i$, (i.e. $f(x_i)$)
498-
/// * `F_DIFF`: precomputed values of $f(x_i) - f(x_{i+1})$
499-
/// * `pdf`: the probability density function
500-
/// * `zero_case`: manual sampling from the tail when we chose the
501-
/// bottom box (i.e. i == 0)
502-
503-
// the perf improvement (25-50%) is definitely worth the extra code
504-
// size from force-inlining.
505-
#[cfg(feature="std")]
506-
#[inline(always)]
507-
fn ziggurat<R: Rng + ?Sized, P, Z>(
508-
rng: &mut R,
509-
symmetric: bool,
510-
x_tab: ziggurat_tables::ZigTable,
511-
f_tab: ziggurat_tables::ZigTable,
512-
mut pdf: P,
513-
mut zero_case: Z)
514-
-> f64 where P: FnMut(f64) -> f64, Z: FnMut(&mut R, f64) -> f64 {
515-
loop {
516-
// As an optimisation we re-implement the conversion to a f64.
517-
// From the remaining 12 most significant bits we use 8 to construct `i`.
518-
// This saves us generating a whole extra random number, while the added
519-
// precision of using 64 bits for f64 does not buy us much.
520-
let bits = rng.next_u64();
521-
let i = bits as usize & 0xff;
522-
523-
let u = if symmetric {
524-
// Convert to a value in the range [2,4) and substract to get [-1,1)
525-
// We can't convert to an open range directly, that would require
526-
// substracting `3.0 - EPSILON`, which is not representable.
527-
// It is possible with an extra step, but an open range does not
528-
// seem neccesary for the ziggurat algorithm anyway.
529-
(bits >> 12).into_float_with_exponent(1) - 3.0
530-
} else {
531-
// Convert to a value in the range [1,2) and substract to get (0,1)
532-
(bits >> 12).into_float_with_exponent(0)
533-
- (1.0 - ::core::f64::EPSILON / 2.0)
534-
};
535-
let x = u * x_tab[i];
536-
537-
let test_x = if symmetric { x.abs() } else {x};
538-
539-
// algebraically equivalent to |u| < x_tab[i+1]/x_tab[i] (or u < x_tab[i+1]/x_tab[i])
540-
if test_x < x_tab[i + 1] {
541-
return x;
542-
}
543-
if i == 0 {
544-
return zero_case(rng, u);
545-
}
546-
// algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1
547-
if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.gen::<f64>() < pdf(x) {
548-
return x;
549-
}
550-
}
551-
}
552-
553467
#[cfg(test)]
554468
mod tests {
555469
use rngs::mock::StepRng;

src/distributions/normal.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
//! The normal and derived distributions.
1212
1313
use Rng;
14-
use distributions::{ziggurat, ziggurat_tables, Distribution, Open01};
14+
use distributions::{ziggurat_tables, Distribution, Open01};
15+
use distributions::utils::ziggurat;
1516

1617
/// Samples floating-point numbers according to the normal distribution
1718
/// `N(0, 1)` (a.k.a. a standard normal, or Gaussian). This is equivalent to

src/distributions/poisson.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
1313
use Rng;
1414
use distributions::{Distribution, Cauchy};
15-
use distributions::log_gamma::log_gamma;
15+
use distributions::utils::log_gamma;
1616

1717
/// The Poisson distribution `Poisson(lambda)`.
1818
///

src/distributions/utils.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
1313
#[cfg(feature="simd_support")]
1414
use core::simd::*;
15+
#[cfg(feature="std")]
16+
use distributions::ziggurat_tables;
17+
#[cfg(feature="std")]
18+
use Rng;
1519

1620

1721
pub trait WideningMultiply<RHS = Self> {
@@ -271,3 +275,110 @@ macro_rules! simd_impl {
271275
#[cfg(feature="simd_support")] simd_impl! { f64x2, f64, m64x2, u64x2 }
272276
#[cfg(feature="simd_support")] simd_impl! { f64x4, f64, m64x4, u64x4 }
273277
#[cfg(feature="simd_support")] simd_impl! { f64x8, f64, m1x8, u64x8 }
278+
279+
/// Calculates ln(gamma(x)) (natural logarithm of the gamma
280+
/// function) using the Lanczos approximation.
281+
///
282+
/// The approximation expresses the gamma function as:
283+
/// `gamma(z+1) = sqrt(2*pi)*(z+g+0.5)^(z+0.5)*exp(-z-g-0.5)*Ag(z)`
284+
/// `g` is an arbitrary constant; we use the approximation with `g=5`.
285+
///
286+
/// Noting that `gamma(z+1) = z*gamma(z)` and applying `ln` to both sides:
287+
/// `ln(gamma(z)) = (z+0.5)*ln(z+g+0.5)-(z+g+0.5) + ln(sqrt(2*pi)*Ag(z)/z)`
288+
///
289+
/// `Ag(z)` is an infinite series with coefficients that can be calculated
290+
/// ahead of time - we use just the first 6 terms, which is good enough
291+
/// for most purposes.
292+
#[cfg(feature="std")]
293+
pub fn log_gamma(x: f64) -> f64 {
294+
// precalculated 6 coefficients for the first 6 terms of the series
295+
let coefficients: [f64; 6] = [
296+
76.18009172947146,
297+
-86.50532032941677,
298+
24.01409824083091,
299+
-1.231739572450155,
300+
0.1208650973866179e-2,
301+
-0.5395239384953e-5,
302+
];
303+
304+
// (x+0.5)*ln(x+g+0.5)-(x+g+0.5)
305+
let tmp = x + 5.5;
306+
let log = (x + 0.5) * tmp.ln() - tmp;
307+
308+
// the first few terms of the series for Ag(x)
309+
let mut a = 1.000000000190015;
310+
let mut denom = x;
311+
for coeff in &coefficients {
312+
denom += 1.0;
313+
a += coeff / denom;
314+
}
315+
316+
// get everything together
317+
// a is Ag(x)
318+
// 2.5066... is sqrt(2pi)
319+
log + (2.5066282746310005 * a / x).ln()
320+
}
321+
322+
/// Sample a random number using the Ziggurat method (specifically the
323+
/// ZIGNOR variant from Doornik 2005). Most of the arguments are
324+
/// directly from the paper:
325+
///
326+
/// * `rng`: source of randomness
327+
/// * `symmetric`: whether this is a symmetric distribution, or one-sided with P(x < 0) = 0.
328+
/// * `X`: the $x_i$ abscissae.
329+
/// * `F`: precomputed values of the PDF at the $x_i$, (i.e. $f(x_i)$)
330+
/// * `F_DIFF`: precomputed values of $f(x_i) - f(x_{i+1})$
331+
/// * `pdf`: the probability density function
332+
/// * `zero_case`: manual sampling from the tail when we chose the
333+
/// bottom box (i.e. i == 0)
334+
335+
// the perf improvement (25-50%) is definitely worth the extra code
336+
// size from force-inlining.
337+
#[cfg(feature="std")]
338+
#[inline(always)]
339+
pub fn ziggurat<R: Rng + ?Sized, P, Z>(
340+
rng: &mut R,
341+
symmetric: bool,
342+
x_tab: ziggurat_tables::ZigTable,
343+
f_tab: ziggurat_tables::ZigTable,
344+
mut pdf: P,
345+
mut zero_case: Z)
346+
-> f64 where P: FnMut(f64) -> f64, Z: FnMut(&mut R, f64) -> f64 {
347+
use distributions::float::IntoFloat;
348+
loop {
349+
// As an optimisation we re-implement the conversion to a f64.
350+
// From the remaining 12 most significant bits we use 8 to construct `i`.
351+
// This saves us generating a whole extra random number, while the added
352+
// precision of using 64 bits for f64 does not buy us much.
353+
let bits = rng.next_u64();
354+
let i = bits as usize & 0xff;
355+
356+
let u = if symmetric {
357+
// Convert to a value in the range [2,4) and substract to get [-1,1)
358+
// We can't convert to an open range directly, that would require
359+
// substracting `3.0 - EPSILON`, which is not representable.
360+
// It is possible with an extra step, but an open range does not
361+
// seem neccesary for the ziggurat algorithm anyway.
362+
(bits >> 12).into_float_with_exponent(1) - 3.0
363+
} else {
364+
// Convert to a value in the range [1,2) and substract to get (0,1)
365+
(bits >> 12).into_float_with_exponent(0)
366+
- (1.0 - ::core::f64::EPSILON / 2.0)
367+
};
368+
let x = u * x_tab[i];
369+
370+
let test_x = if symmetric { x.abs() } else {x};
371+
372+
// algebraically equivalent to |u| < x_tab[i+1]/x_tab[i] (or u < x_tab[i+1]/x_tab[i])
373+
if test_x < x_tab[i + 1] {
374+
return x;
375+
}
376+
if i == 0 {
377+
return zero_case(rng, u);
378+
}
379+
// algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1
380+
if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.gen::<f64>() < pdf(x) {
381+
return x;
382+
}
383+
}
384+
}

src/distributions/weighted.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,16 @@ mod test {
169169
}
170170
}
171171

172+
/// Error type returned from `WeightedIndex::new`.
172173
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173174
pub enum WeightedError {
175+
/// The provided iterator contained no items.
174176
NoItem,
177+
178+
/// A weight lower than zero was used.
175179
NegativeWeight,
180+
181+
/// All items in the provided iterator had a weight of zero.
176182
AllWeightsZero,
177183
}
178184

0 commit comments

Comments
 (0)