rand_distr/utils.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
// Copyright 2018 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Math helper functions
use rand::Rng;
use crate::ziggurat_tables;
use rand::distributions::hidden_export::IntoFloat;
use core::{cmp, ops};
/// Trait for floating-point scalar types
///
/// This allows many distributions to work with `f32` or `f64` parameters and is
/// potentially extensible. Note however that the `Exp1` and `StandardNormal`
/// distributions are implemented exclusively for `f32` and `f64`.
///
/// The bounds and methods are based purely on internal
/// requirements, and will change as needed.
pub trait Float: Copy + Sized + cmp::PartialOrd
+ ops::Neg<Output = Self>
+ ops::Add<Output = Self>
+ ops::Sub<Output = Self>
+ ops::Mul<Output = Self>
+ ops::Div<Output = Self>
+ ops::AddAssign + ops::SubAssign + ops::MulAssign + ops::DivAssign
{
/// The constant π
fn pi() -> Self;
/// Support approximate representation of a f64 value
fn from(x: f64) -> Self;
/// Support converting to an unsigned integer.
fn to_u64(self) -> Option<u64>;
/// Take the absolute value of self
fn abs(self) -> Self;
/// Take the largest integer less than or equal to self
fn floor(self) -> Self;
/// Take the exponential of self
fn exp(self) -> Self;
/// Take the natural logarithm of self
fn ln(self) -> Self;
/// Take square root of self
fn sqrt(self) -> Self;
/// Take self to a floating-point power
fn powf(self, power: Self) -> Self;
/// Take the tangent of self
fn tan(self) -> Self;
/// Take the logarithm of the gamma function of self
fn log_gamma(self) -> Self;
}
impl Float for f32 {
#[inline]
fn pi() -> Self { core::f32::consts::PI }
#[inline]
fn from(x: f64) -> Self { x as f32 }
#[inline]
fn to_u64(self) -> Option<u64> {
if self >= 0. && self <= ::core::u64::MAX as f32 {
Some(self as u64)
} else {
None
}
}
#[inline]
fn abs(self) -> Self { self.abs() }
#[inline]
fn floor(self) -> Self { self.floor() }
#[inline]
fn exp(self) -> Self { self.exp() }
#[inline]
fn ln(self) -> Self { self.ln() }
#[inline]
fn sqrt(self) -> Self { self.sqrt() }
#[inline]
fn powf(self, power: Self) -> Self { self.powf(power) }
#[inline]
fn tan(self) -> Self { self.tan() }
#[inline]
fn log_gamma(self) -> Self {
let result = log_gamma(self.into());
assert!(result <= ::core::f32::MAX.into());
assert!(result >= ::core::f32::MIN.into());
result as f32
}
}
impl Float for f64 {
#[inline]
fn pi() -> Self { core::f64::consts::PI }
#[inline]
fn from(x: f64) -> Self { x }
#[inline]
fn to_u64(self) -> Option<u64> {
if self >= 0. && self <= ::core::u64::MAX as f64 {
Some(self as u64)
} else {
None
}
}
#[inline]
fn abs(self) -> Self { self.abs() }
#[inline]
fn floor(self) -> Self { self.floor() }
#[inline]
fn exp(self) -> Self { self.exp() }
#[inline]
fn ln(self) -> Self { self.ln() }
#[inline]
fn sqrt(self) -> Self { self.sqrt() }
#[inline]
fn powf(self, power: Self) -> Self { self.powf(power) }
#[inline]
fn tan(self) -> Self { self.tan() }
#[inline]
fn log_gamma(self) -> Self { log_gamma(self) }
}
/// Calculates ln(gamma(x)) (natural logarithm of the gamma
/// function) using the Lanczos approximation.
///
/// The approximation expresses the gamma function as:
/// `gamma(z+1) = sqrt(2*pi)*(z+g+0.5)^(z+0.5)*exp(-z-g-0.5)*Ag(z)`
/// `g` is an arbitrary constant; we use the approximation with `g=5`.
///
/// Noting that `gamma(z+1) = z*gamma(z)` and applying `ln` to both sides:
/// `ln(gamma(z)) = (z+0.5)*ln(z+g+0.5)-(z+g+0.5) + ln(sqrt(2*pi)*Ag(z)/z)`
///
/// `Ag(z)` is an infinite series with coefficients that can be calculated
/// ahead of time - we use just the first 6 terms, which is good enough
/// for most purposes.
pub(crate) fn log_gamma(x: f64) -> f64 {
// precalculated 6 coefficients for the first 6 terms of the series
let coefficients: [f64; 6] = [
76.18009172947146,
-86.50532032941677,
24.01409824083091,
-1.231739572450155,
0.1208650973866179e-2,
-0.5395239384953e-5,
];
// (x+0.5)*ln(x+g+0.5)-(x+g+0.5)
let tmp = x + 5.5;
let log = (x + 0.5) * tmp.ln() - tmp;
// the first few terms of the series for Ag(x)
let mut a = 1.000000000190015;
let mut denom = x;
for &coeff in &coefficients {
denom += 1.0;
a += coeff / denom;
}
// get everything together
// a is Ag(x)
// 2.5066... is sqrt(2pi)
log + (2.5066282746310005 * a / x).ln()
}
/// Sample a random number using the Ziggurat method (specifically the
/// ZIGNOR variant from Doornik 2005). Most of the arguments are
/// directly from the paper:
///
/// * `rng`: source of randomness
/// * `symmetric`: whether this is a symmetric distribution, or one-sided with P(x < 0) = 0.
/// * `X`: the $x_i$ abscissae.
/// * `F`: precomputed values of the PDF at the $x_i$, (i.e. $f(x_i)$)
/// * `F_DIFF`: precomputed values of $f(x_i) - f(x_{i+1})$
/// * `pdf`: the probability density function
/// * `zero_case`: manual sampling from the tail when we chose the
/// bottom box (i.e. i == 0)
// the perf improvement (25-50%) is definitely worth the extra code
// size from force-inlining.
#[inline(always)]
pub(crate) fn ziggurat<R: Rng + ?Sized, P, Z>(
rng: &mut R,
symmetric: bool,
x_tab: ziggurat_tables::ZigTable,
f_tab: ziggurat_tables::ZigTable,
mut pdf: P,
mut zero_case: Z)
-> f64 where P: FnMut(f64) -> f64, Z: FnMut(&mut R, f64) -> f64 {
loop {
// As an optimisation we re-implement the conversion to a f64.
// From the remaining 12 most significant bits we use 8 to construct `i`.
// This saves us generating a whole extra random number, while the added
// precision of using 64 bits for f64 does not buy us much.
let bits = rng.next_u64();
let i = bits as usize & 0xff;
let u = if symmetric {
// Convert to a value in the range [2,4) and substract to get [-1,1)
// We can't convert to an open range directly, that would require
// substracting `3.0 - EPSILON`, which is not representable.
// It is possible with an extra step, but an open range does not
// seem neccesary for the ziggurat algorithm anyway.
(bits >> 12).into_float_with_exponent(1) - 3.0
} else {
// Convert to a value in the range [1,2) and substract to get (0,1)
(bits >> 12).into_float_with_exponent(0)
- (1.0 - std::f64::EPSILON / 2.0)
};
let x = u * x_tab[i];
let test_x = if symmetric { x.abs() } else {x};
// algebraically equivalent to |u| < x_tab[i+1]/x_tab[i] (or u < x_tab[i+1]/x_tab[i])
if test_x < x_tab[i + 1] {
return x;
}
if i == 0 {
return zero_case(rng, u);
}
// algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1
if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.gen::<f64>() < pdf(x) {
return x;
}
}
}