/** * \file MPFRNormalK.hpp * \brief Header for MPFRNormalK * * Sampling exactly from the normal distribution for MPFR. * * Copyright (c) Charles Karney (2012) and licensed under * the MIT/X11 License. For more information, see * http://randomlib.sourceforge.net/ **********************************************************************/ #if !defined(RANDOMLIB_MPFRNORMALK_HPP) #define RANDOMLIB_MPFRNORMALK_HPP 1 #include // for max #include #include #if HAVE_MPFR || defined(DOXYGEN) namespace RandomLib { /** * \brief The normal distribution for MPFR (Kahn algorithm). * * This class is DEPRECATED. It is included for illustrative purposes * only. The MPFRNormal class provides a somewhat more efficient method for * sampling from the normal distribution. * * Refs: * - H. Kahn, Rand Report RM-1237-AEC, p. 41 (1954). * - M. Abramowitz and I. A. Stegun, p. 953, Sec. 26.8.6.a(4) (1964). * . * N.B. Damien Stehle' drew my attention to this algorithm as a useful way to * compute normal deviates exactly. * * This class uses mutable private objects. So a single MPFRNormalK object * cannot safely be used by multiple threads. In a multi-processing * environment, each thread should use a thread-specific MPFRNormalK object. * * @tparam bits the number of bits in each digit. **********************************************************************/ template class MPFRNormalK { public: /** * Initialize the MPFRNormalK object. **********************************************************************/ MPFRNormalK() { mpfr_init2(_xf, MPFR_PREC_MIN); mpfr_init2(_zf, MPFR_PREC_MIN); } /** * Destroy the MPFRNormalK object. **********************************************************************/ ~MPFRNormalK() { mpfr_clear(_zf); mpfr_clear(_xf); } /** * Sample from the normal distribution with mean 0 and variance 1 returning * a MPFRRandom. * * @param[out] t the MPFRRandom result. * @param[in,out] r a GMP random generator. **********************************************************************/ void operator()(MPFRRandom& t, gmp_randstate_t r) const { Compute(r); _x.swap(t); } /** * Sample from the normal distribution with mean 0 and variance 1. * * @param[out] val the sample from the normal distribution * @param[in,out] r a GMP random generator. * @param[in] round the rounding direction. * @return the MPFR ternary result (±1 if val is larger/smaller than * the exact sample). **********************************************************************/ int operator()(mpfr_t val, gmp_randstate_t r, mpfr_rnd_t round) const { Compute(r); return _x(val, r, round); } private: // disable copy constructor and assignment operator MPFRNormalK(const MPFRNormalK&); MPFRNormalK& operator=(const MPFRNormalK&); void Compute(gmp_randstate_t r) const { // The algorithm is sample x and z from the exponential distribution; if // (x-1)^2 < 2*z, return (random sign)*x; otherwise repeat. Probability // of acceptance is sqrt(pi/2) * exp(-1/2) = 0.7602. while (true) { _edist(_x, r); _edist(_z, r); for (mp_size_t k = 1; ; ++k) { _x.ExpandTo(r, k - 1); _z.ExpandTo(r, k - 1); mpfr_prec_t prec = (std::max)(mpfr_prec_t(MPFR_PREC_MIN), k * bits); mpfr_set_prec(_xf, prec); mpfr_set_prec(_zf, prec); // Try for acceptance first; so compute upper limit on (y-1)^2 and // lower limit on 2*z. if (_x.UInteger() == 0) { _x(_xf, MPFR_RNDD); mpfr_ui_sub(_xf, 1u, _xf, MPFR_RNDU); } else { _x(_xf, MPFR_RNDU); mpfr_sub_ui(_xf, _xf, 1u, MPFR_RNDU); } mpfr_sqr(_xf, _xf, MPFR_RNDU); _z(_zf, MPFR_RNDD); mpfr_mul_2ui(_zf, _zf, 1u, MPFR_RNDD); if (mpfr_cmp(_xf, _zf) < 0) { // (y-1)^2 < 2*z, so accept if (_x.Boolean(r)) _x.Negate(); // include a random sign return; } // Try for rejection; so compute lower limit on (y-1)^2 and upper // limit on 2*z. if (_x.UInteger() == 0) { _x(_xf, MPFR_RNDU); mpfr_ui_sub(_xf, 1u, _xf, MPFR_RNDD); } else { _x(_xf, MPFR_RNDD); mpfr_sub_ui(_xf, _xf, 1u, MPFR_RNDD); } mpfr_sqr(_xf, _xf, MPFR_RNDD); _z(_zf, MPFR_RNDU); mpfr_mul_2ui(_zf, _zf, 1u, MPFR_RNDU); if (mpfr_cmp(_xf, _zf) > 0) // (y-1)^2 > 2*z, so reject break; // Otherwise repeat with more precision } // Reject and start over with a new y and z } } mutable MPFRRandom _x; mutable MPFRRandom _z; mutable mpfr_t _xf; mutable mpfr_t _zf; const MPFRExponential _edist; }; } // namespace RandomLib #endif // HAVE_MPFR #endif // RANDOMLIB_MPFRNORMALK_HPP