diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2019-09-26 01:54:45 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-09-26 01:54:45 -0400 |
| commit | 68bb3945708c43109c48bda3664176307d45b62c (patch) | |
| tree | afb9731e10cef9d192560b52cd9ae2cf179775c4 /lib/std/rand | |
| parent | 6128bc728d1e1024a178c16c2149f5b1a167a013 (diff) | |
| parent | 4637e8f9699af9c3c6cf4df50ef5bb67c7a318a4 (diff) | |
| download | zig-68bb3945708c43109c48bda3664176307d45b62c.tar.gz zig-68bb3945708c43109c48bda3664176307d45b62c.zip | |
Merge pull request #3315 from ziglang/mv-std-lib
Move std/ to lib/std/
Diffstat (limited to 'lib/std/rand')
| -rw-r--r-- | lib/std/rand/ziggurat.zig | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/lib/std/rand/ziggurat.zig b/lib/std/rand/ziggurat.zig new file mode 100644 index 0000000000..995248415b --- /dev/null +++ b/lib/std/rand/ziggurat.zig @@ -0,0 +1,166 @@ +// Implements ZIGNOR [1]. +// +// [1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to Generate Normal Random Samples*] +// (https://www.doornik.com/research/ziggurat.pdf). Nuffield College, Oxford. +// +// rust/rand used as a reference; +// +// NOTE: This seems interesting but reference code is a bit hard to grok: +// https://sbarral.github.io/etf. + +const std = @import("../std.zig"); +const math = std.math; +const Random = std.rand.Random; + +pub fn next_f64(random: *Random, comptime tables: ZigTable) f64 { + while (true) { + // We manually construct a float from parts as we can avoid an extra random lookup here by + // using the unused exponent for the lookup table entry. + const bits = random.scalar(u64); + const i = usize(bits & 0xff); + + const u = blk: { + if (tables.is_symmetric) { + // Generate a value in the range [2, 4) and scale into [-1, 1) + const repr = ((0x3ff + 1) << 52) | (bits >> 12); + break :blk @bitCast(f64, repr) - 3.0; + } else { + // Generate a value in the range [1, 2) and scale into (0, 1) + const repr = (0x3ff << 52) | (bits >> 12); + break :blk @bitCast(f64, repr) - (1.0 - math.f64_epsilon / 2.0); + } + }; + + const x = u * tables.x[i]; + const test_x = if (tables.is_symmetric) math.fabs(x) else x; + + // equivalent to |u| < tables.x[i+1] / tables.x[i] (or u < tables.x[i+1] / tables.x[i]) + if (test_x < tables.x[i + 1]) { + return x; + } + + if (i == 0) { + return tables.zero_case(random, u); + } + + // equivalent to f1 + DRanU() * (f0 - f1) < 1 + if (tables.f[i + 1] + (tables.f[i] - tables.f[i + 1]) * random.float(f64) < tables.pdf(x)) { + return x; + } + } +} + +pub const ZigTable = struct { + r: f64, + x: [257]f64, + f: [257]f64, + + // probability density function used as a fallback + pdf: fn (f64) f64, + // whether the distribution is symmetric + is_symmetric: bool, + // fallback calculation in the case we are in the 0 block + zero_case: fn (*Random, f64) f64, +}; + +// zigNorInit +fn ZigTableGen( + comptime is_symmetric: bool, + comptime r: f64, + comptime v: f64, + comptime f: fn (f64) f64, + comptime f_inv: fn (f64) f64, + comptime zero_case: fn (*Random, f64) f64, +) ZigTable { + var tables: ZigTable = undefined; + + tables.is_symmetric = is_symmetric; + tables.r = r; + tables.pdf = f; + tables.zero_case = zero_case; + + tables.x[0] = v / f(r); + tables.x[1] = r; + + for (tables.x[2..256]) |*entry, i| { + const last = tables.x[2 + i - 1]; + entry.* = f_inv(v / last + f(last)); + } + tables.x[256] = 0; + + for (tables.f[0..]) |*entry, i| { + entry.* = f(tables.x[i]); + } + + return tables; +} + +// N(0, 1) +pub const NormDist = blk: { + @setEvalBranchQuota(30000); + break :blk ZigTableGen(true, norm_r, norm_v, norm_f, norm_f_inv, norm_zero_case); +}; + +const norm_r = 3.6541528853610088; +const norm_v = 0.00492867323399; + +fn norm_f(x: f64) f64 { + return math.exp(-x * x / 2.0); +} +fn norm_f_inv(y: f64) f64 { + return math.sqrt(-2.0 * math.ln(y)); +} +fn norm_zero_case(random: *Random, u: f64) f64 { + var x: f64 = 1; + var y: f64 = 0; + + while (-2.0 * y < x * x) { + x = math.ln(random.float(f64)) / norm_r; + y = math.ln(random.float(f64)); + } + + if (u < 0) { + return x - norm_r; + } else { + return norm_r - x; + } +} + +test "ziggurant normal dist sanity" { + var prng = std.rand.DefaultPrng.init(0); + var i: usize = 0; + while (i < 1000) : (i += 1) { + _ = prng.random.floatNorm(f64); + } +} + +// Exp(1) +pub const ExpDist = blk: { + @setEvalBranchQuota(30000); + break :blk ZigTableGen(false, exp_r, exp_v, exp_f, exp_f_inv, exp_zero_case); +}; + +const exp_r = 7.69711747013104972; +const exp_v = 0.0039496598225815571993; + +fn exp_f(x: f64) f64 { + return math.exp(-x); +} +fn exp_f_inv(y: f64) f64 { + return -math.ln(y); +} +fn exp_zero_case(random: *Random, _: f64) f64 { + return exp_r - math.ln(random.float(f64)); +} + +test "ziggurant exp dist sanity" { + var prng = std.rand.DefaultPrng.init(0); + var i: usize = 0; + while (i < 1000) : (i += 1) { + _ = prng.random.floatExp(f64); + } +} + +test "ziggurat table gen" { + const table = NormDist; +} |
