/** * \file DiscreteNormalAlt.hpp * \brief Header for DiscreteNormalAlt * * Sample exactly from the discrete normal distribution (alternate version). * * Copyright (c) Charles Karney (2013) and licensed * under the MIT/X11 License. For more information, see * http://randomlib.sourceforge.net/ **********************************************************************/ #if !defined(RANDOMLIB_DISCRETENORMALALT_HPP) #define RANDOMLIB_DISCRETENORMALALT_HPP 1 #include #include #include namespace RandomLib { /** * \brief The discrete normal distribution (alternate version). * * Sample integers \e i with probability proportional to * \f[ * \exp\biggl[-\frac12\biggl(\frac{i-\mu}{\sigma}\biggr)^2\biggr], * \f] * where σ and μ are given as rationals (the ratio of two integers). * The sampling is exact (provided that the random generator is ideal). The * results of this class are UniformInteger objects which represent a * contiguous range which is a power of 2\e bits. This can be * narrowed down to a specific integer as follows * \code #include #include #include #include int main() { RandomLib::Random r; // Create r r.Reseed(); // and give it a unique seed int sigma_num = 7, sigma_den = 1, mu_num = 1, mu_den = 3; RandomLib::DiscreteNormalAlt d(sigma_num, sigma_den, mu_num, mu_den); for (int i = 0; i < 100; ++i) { RandomLib::UniformInteger u = d(r); std::cout << u << " = "; std::cout << u(r) << "\n"; } } \endcode * prints out 100 samples with σ = 7 and μ = 1/3; the samples are * first given as a range and then u(r) is used to obtain a * specific integer. In some applications, it might be possible to avoid * sampling all the additional digits to get to a specific integer. (An * example would be drawing a sample which satisfies an inequality.) This * version is slower (by a factor of about 4 for \e bits = 1) than * DiscreteNormal on a conventional computer, but may be faster when random * bits are generated by a slow hardware device. * * The basic algorithm is the same as for DiscreteNormal. However randomness * in metered out \e bits random bits at a time. The algorithm uses the * least random bits (and is slowest!) when \e bits = 1. This rationing of * random bits also applies to sampling \e j in the expression * \f[ * x = x_0 + j/\sigma. * \f] * Rather than sampling a definite value for \e j, which then might be * rejected, \e j is sampled using UniformInteger. UniformInteger uses * Lumbroso's method from sampling an integer uniformly in [0, \e m) using at * most 2 + log2\e m bits on average (for \e bits = 1). However * when a UniformInteger object is first constructed it samples at most 3 * bits (on average) to obtain a range for \e j which is power of 2. This * allows the algorithm to achieve ideal scaling in the limit of large * σ consuming constant + log2σ bits on average. In * addition it can deliver the resuls in the form of a UniformInteger * consuming a constant number of bits on average (and then about * log2σ additional bits are required to convert the * UniformInteger into an integer sample). The consumption of random bits * (for \e bits = 1) is summarized here \verbatim min mean max bits for UniformInteger result 30.4 34.3 35.7 bits for integer result - log2(sigma) 28.8 31.2 32.5 toll 26.8 29.1 30.4 \endverbatim * These results are averaged over many samples. The "min" results apply * when σ is a power of 2; the "max" results apply when σ is * slightly more than a power of two; the "mean" results are averaged over * σ. The toll is the excess number of bits over the entropy of the * distribution, which is log2(2π\e e)/2 + * log2σ (for σ large). * * The data for the first two lines in the table above are taken for the blue * and red lines in the figure below where the abscissa is the fractional * part of log2σ * \image html * discrete-bits.png "Random bits to sample from discrete normal distribution" * * This class uses a mutable RandomNumber objects. So a single * DiscreteNormalAlt object cannot safely be used by multiple threads. In a * multi-processing environment, each thread should use a thread-specific * DiscreteNormalAlt object. * * @tparam IntType the integer type to use (default int). **********************************************************************/ template class DiscreteNormalAlt { public: /** * Constructor. * * @param[in] sigma_num the numerator of σ. * @param[in] sigma_den the denominator of σ (default 1). * @param[in] mu_num the numerator of μ (default 0). * @param[in] mu_den the denominator of μ (default 1). * * This may throw an exception is the parameters are such that overflow is * possible. **********************************************************************/ DiscreteNormalAlt(IntType sigma_num, IntType sigma_den = 1, IntType mu_num = 0, IntType mu_den = 1); /** * Return a sample. * * @tparam Random the type of the random generator. * @param[in,out] r a random generator. * @return discrete normal sample in the form of a UniformInteger object. **********************************************************************/ template UniformInteger operator()(Random& r) const; private: /** * sigma = _sig / _d, mu = _imu + _mu / _d, _isig = ceil(sigma) **********************************************************************/ IntType _sig, _mu, _d, _isig, _imu; mutable RandomNumber _p; mutable RandomNumber _q; // ceil(n/d) for d > 0 static IntType iceil(IntType n, IntType d); // abs(n) needed because Visual Studio's std::abs has problems static IntType iabs(IntType n); static IntType gcd(IntType u, IntType v); /** * Implement outcomes for choosing with prob (\e x + 2\e k) / (2\e k + 2); * return: * - 1 (succeed unconditionally) with prob (\e m − 2) / \e m, * - 0 (succeed with probability x) with prob 1 / \e m, * - −1 (fail unconditionally) with prob 1 / \e m. **********************************************************************/ template static int Choose(Random& r, int m); /** * Return true with probability exp(−1/2). **********************************************************************/ template bool ExpProbH(Random& r) const; /** * Return true with probability exp(−n/2). **********************************************************************/ template bool ExpProb(Random& r, int n) const; /** * Return \e n with probability exp(−n/2) * (1−exp(−1/2)). **********************************************************************/ template int ExpProbN(Random& r) const; /** * Algorithm B: true with prob exp(-x * (2*k + x) / (2*k + 2)) where * x = (xn0 + _d * j) / _sig **********************************************************************/ template bool B(Random& r, int k, IntType xn0, UniformInteger& j) const; }; template DiscreteNormalAlt::DiscreteNormalAlt (IntType sigma_num, IntType sigma_den, IntType mu_num, IntType mu_den) { STATIC_ASSERT(std::numeric_limits::is_integer, "DiscreteNormalAlt: invalid integer type IntType"); STATIC_ASSERT(std::numeric_limits::is_signed, "DiscreteNormalAlt: IntType must be a signed type"); if (!( sigma_num > 0 && sigma_den > 0 && mu_den > 0 )) throw RandomErr("DiscreteNormalAlt: need sigma > 0"); _imu = mu_num / mu_den; if (_imu == (std::numeric_limits::min)()) throw RandomErr("DiscreteNormalAlt: abs(mu) too large"); mu_num -= _imu * mu_den; IntType l; l = gcd(sigma_num, sigma_den); sigma_num /= l; sigma_den /= l; l = gcd(mu_num, mu_den); mu_num /= l; mu_den /= l; _isig = iceil(sigma_num, sigma_den); l = gcd(sigma_den, mu_den); _sig = sigma_num * (mu_den / l); _mu = mu_num * (sigma_den / l); _d = sigma_den * (mu_den / l); // The rest of the constructor tests for possible overflow // Check for overflow in computing member variables IntType maxint = (std::numeric_limits::max)(); if (!( mu_den / l <= maxint / sigma_num && iabs(mu_num) <= maxint / (sigma_den / l) && mu_den / l <= maxint / sigma_den )) throw RandomErr("DiscreteNormalAlt: sigma or mu overflow"); if (!UniformInteger::Check(_isig, _d)) throw RandomErr("DiscreteNormalAlt: overflow in UniformInteger"); // The probability that k = kmax is about 10^-543. int kmax = 50; // Check that max plausible result fits in an IntType, i.e., // _isig * (kmax + 1) + abs(_imu) does not lead to overflow. if (!( kmax + 1 <= maxint / _isig && _isig * (kmax + 1) <= maxint - iabs(_imu) )) throw RandomErr("DiscreteNormalAlt: possible overflow a"); // Check xn0 = _sig * k + s * _mu; if (!( kmax <= maxint / _sig && _sig * kmax <= maxint - iabs(_mu) )) throw RandomErr("DiscreteNormalAlt: possible overflow b"); // Check for overflow in RandomNumber::LessThan(..., UniformInteger& j) // p0 * b, p0 = arg2 = xn0 = _d - 1 // c *= b, c = arg3 = _d // digit(D, k) * q, digit(D, k) = b - 1, q = arg4 = _sig if (!( _d <= maxint >> bits )) throw std::runtime_error("DiscreteNormalAlt: overflow in RandomNumber a"); if (!( (IntType(1) << bits) - 1 <= maxint / _sig )) throw std::runtime_error("DiscreteNormalAlt: overflow in RandomNumber b"); } template template UniformInteger DiscreteNormalAlt::operator()(Random& r) const { for (;;) { int k = ExpProbN(r); if (!ExpProb(r, k * (k - 1))) continue; UniformInteger j(r, _isig); IntType s = r.Boolean() ? -1 : 1, xn0 = _sig * IntType(k) + s * _mu, i0 = iceil(xn0, _d); // i = i0 + j xn0 = i0 * _d - xn0; // xn = xn0 + _d * j if (!j.LessThan(r, _sig - xn0, _d) || (k == 0 && s < 0 && !j.GreaterThan(r, -xn0, _d))) continue; int h = k + 1; while (h-- && B(r, k, xn0, j)); if (!(h < 0)) continue; j.Add(i0 + s * _imu); if (s < 0) j.Negate(); return j; } } template IntType DiscreteNormalAlt::iceil(IntType n, IntType d) { IntType k = n / d; return k + (k * d < n ? 1 : 0); } template IntType DiscreteNormalAlt::iabs(IntType n) { return n < 0 ? -n : n; } template IntType DiscreteNormalAlt::gcd(IntType u, IntType v) { // Knuth, TAOCP, vol 2, 4.5.2, Algorithm A u = iabs(u); v = iabs(v); while (v > 0) { IntType r = u % v; u = v; v = r; } return u; } template template int DiscreteNormalAlt::Choose(Random& r, int m) { // Limit base to 2^15 to avoid integer overflow const int b = bits > 15 ? 15 : bits; const unsigned mask = (1u << b) - 1; int n1 = m - 2, n2 = m - 1; // Evaluate u < n/m where u is a random real number in [0,1). Write u = // (d + u') / 2^b where d is a random integer in [0,2^b) and u' is in // [0,1). Then u < n/m becomes u' < n'/m where n' = 2^b * n - d * m and // exit if n' <= 0 (false) or n' >= m (true). for (;;) { int d = (mask & RandomNumber::RandomDigit(r)) * m; n1 = (std::max)((n1 << b) - d, 0); if (n1 >= m) return 1; n2 = (std::min)((n2 << b) - d, m); if (n2 <= 0) return -1; if (n1 == 0 && n2 == m) return 0; } } template template bool DiscreteNormalAlt::ExpProbH(Random& r) const { _p.Init(); if (_p.Digit(r, 0) >> (bits - 1)) return true; for (;;) { _q.Init(); if (!_q.LessThan(r, _p)) return false; _p.Init(); if (!_p.LessThan(r, _q)) return true; } } template template bool DiscreteNormalAlt::ExpProb(Random& r, int n) const { while (n-- > 0) { if (!ExpProbH(r)) return false; } return true; } template template int DiscreteNormalAlt::ExpProbN(Random& r) const { int n = 0; while (ExpProbH(r)) ++n; return n; } template template bool DiscreteNormalAlt::B(Random& r, int k, IntType xn0, UniformInteger& j) const { int n = 0, m = 2 * k + 2, f; for (;; ++n) { if ( ((f = k ? 0 : Choose(r, m)) < 0) || (_q.Init(), !(n ? _q.LessThan(r, _p) : _q.LessThan(r, xn0, _d, _sig, j))) || ((f = k ? Choose(r, m) : f) < 0) || (f == 0 && (_p.Init(), !_p.LessThan(r, xn0, _d, _sig, j))) ) break; _p.swap(_q); // an efficient way of doing p = q } return (n % 2) == 0; } } // namespace RandomLib #endif // RANDOMLIB_DISCRETENORMALALT_HPP