// Reference: // "Computation of the incomplete gamma function ratios and their inverse" // Armido R DiDonato and Alfred H Morris, Jr. // ACM Transactions on Mathematical Software (TOMS) // Volume 12 Issue 4, Dec. 1986 // http://dl.acm.org/citation.cfm?id=23109 // and section 8.3 of // "Numerical Methods for Special Functions" // Amparo Gil, Javier Segura, Nico M. Temme, 2007 /// <summary> /// Compute the regularized lower incomplete Gamma function: <c>int_0^x t^(a-1) exp(-t) dt / Gamma(a)</c> /// </summary> /// <param name="a">Must be > 20</param> /// <param name="x"></param> /// <param name="upper">If true, compute the upper incomplete Gamma function</param> /// <returns></returns> private static double GammaAsympt(double a, double x, bool upper) { if (a <= 20) { throw new Exception("a <= 20"); } double xOverAMinus1 = (x - a) / a; double phi = xOverAMinus1 - MMath.Log1Plus(xOverAMinus1); double y = a * phi; double z = Math.Sqrt(2 * phi); if (x <= a) { z *= -1; } double[] b = new double[GammaLowerAsympt_C0.Length]; b[b.Length - 1] = GammaLowerAsympt_C0[b.Length - 1]; double sum = b[b.Length - 1]; b[b.Length - 2] = GammaLowerAsympt_C0[b.Length - 2]; sum = z * sum + b[b.Length - 2]; for (int i = b.Length - 3; i >= 0; i--) { b[i] = b[i + 2] * (i + 2) / a + GammaLowerAsympt_C0[i]; sum = z * sum + b[i]; } sum *= a / (a + b[1]); if (x <= a) { sum *= -1; } double result = 0.5 * Erfc(Math.Sqrt(y)) + sum * Math.Exp(-y) / (Math.Sqrt(a) * MMath.Sqrt2PI); return(((x > a) == upper) ? result : 1 - result); }
/// <summary> /// Sample from a Gaussian(0,1) truncated at the given upper and lower bounds /// </summary> /// <param name="lowerBound">Can be -Infinity.</param> /// <param name="upperBound">Must be >= <paramref name="lowerBound"/>. Can be Infinity.</param> /// <returns>A real number >= <paramref name="lowerBound"/> and < <paramref name="upperBound"/></returns> public static double NormalBetween(double lowerBound, double upperBound, IPolyrand random = null) { if (double.IsNaN(lowerBound)) { throw new ArgumentException("lowerBound is NaN"); } if (double.IsNaN(upperBound)) { throw new ArgumentException("upperBound is NaN"); } double delta = upperBound - lowerBound; if (delta == 0) { return(lowerBound); } if (delta < 0) { throw new ArgumentException("upperBound (" + upperBound + ") < lowerBound (" + lowerBound + ")"); } // Switch between the following 3 options: // 1. Gaussian rejection, with acceptance rate Z = NormalCdf(upperBound) - NormalCdf(lowerBound) // 2. Uniform rejection, with acceptance rate sqrt(2*pi)*Z/delta if the interval contains 0 // 3. Truncated exponential rejection, with acceptance rate // = sqrt(2*pi)*Z*lambda*exp(-lambda^2/2)/(exp(-lambda*lowerBound)-exp(-lambda*upperBound)) // = sqrt(2*pi)*Z*lowerBound*exp(lowerBound^2/2)/(1-exp(-lowerBound*(upperBound-lowerBound))) // (3) has the highest acceptance rate under the following conditions: // lowerBound > 0.5 or (lowerBound > 0 and delta < 2.5) // (2) has the highest acceptance rate if the interval contains 0 and delta < sqrt(2*pi) // (1) has the highest acceptance rate otherwise if (lowerBound > 0.5 || (lowerBound > 0 && delta < 2.5)) { // Rejection sampler using truncated exponential proposal double lambda = lowerBound; double s = MMath.ExpMinus1(-lambda * delta); double c = 2 * lambda * lambda; while (true) { double x = -MMath.Log1Plus(s * NextDouble(random)); double u = -System.Math.Log(NextDouble(random)); if (c * u > x * x) { return(x / lambda + lowerBound); } } throw new Exception("failed to sample"); } else if (upperBound < -0.5 || (upperBound < 0 && delta < 2.5)) { return(-NormalBetween(-upperBound, -lowerBound)); } else if (lowerBound <= 0 && upperBound >= 0 && delta < MMath.Sqrt2PI) { // Uniform rejection while (true) { double x = NextDouble(random) * delta + lowerBound; double u = -System.Math.Log(NextDouble(random)); if (2 * u > x * x) { return(x); } } } else { // Gaussian rejection while (true) { double x = Rand.Normal(random); if (x >= lowerBound && x < upperBound) { return(x); } } } }