/// <summary> /// Computes <c>GammaUpper(s,x)/(x^(s-1)*exp(-x)) - 1</c> to high accuracy /// </summary> /// <param name="s"></param> /// <param name="x">A real number gt;= 45 and gt; <paramref name="s"/>/0.99</param> /// <param name="regularized"></param> /// <returns></returns> public static double GammaUpperRatio(double s, double x, bool regularized = true) { if (s >= x * 0.99) { throw new ArgumentOutOfRangeException(nameof(s), s, "s >= x*0.99"); } if (x < 45) { throw new ArgumentOutOfRangeException(nameof(x), x, "x < 45"); } double term = (s - 1) / x; double sum = term; for (int i = 2; i < 1000; i++) { term *= (s - i) / x; double oldSum = sum; sum += term; if (MMath.AreEqual(sum, oldSum)) { return(regularized ? sum / MMath.Gamma(s) : sum); } } throw new Exception($"GammaUpperRatio not converging for s={s:g17}, x={x:g17}, regularized={regularized}"); }
/// <summary> /// Constructs a Gamma distribution with the given mean and mean logarithm. /// </summary> /// <param name="mean">Desired expected value.</param> /// <param name="logMeanMinusMeanLog">Logarithm of desired expected value minus desired expected logarithm.</param> /// <returns>A new Gamma distribution.</returns> /// <remarks>This function is equivalent to maximum-likelihood estimation of a Gamma distribution /// from data given by sufficient statistics. /// This function is significantly slower than the other constructors since it /// involves nonlinear optimization. The algorithm is a generalized Newton iteration, /// described in "Estimating a Gamma distribution" by T. Minka, 2002. /// </remarks> public static Gamma FromLogMeanMinusMeanLog(double mean, double logMeanMinusMeanLog) { if (logMeanMinusMeanLog <= 0) { return(Gamma.PointMass(mean)); } double shape = 0.5 / logMeanMinusMeanLog; for (int iter = 0; iter < 100; iter++) { double g = LogMinusDigamma(shape) - logMeanMinusMeanLog; //Trace.WriteLine($"shape = {shape} g = {g}"); if (MMath.AreEqual(g, 0)) { break; } shape /= 1 + g / (1 - shape * MMath.Trigamma(shape)); } if (double.IsNaN(shape)) { throw new InferRuntimeException("shape is nan"); } if (shape > double.MaxValue) { return(Gamma.PointMass(mean)); } return(Gamma.FromShapeAndRate(shape, shape / mean)); }
/// <summary> /// Constructs a GammaPower distribution with the given mean and mean logarithm. /// </summary> /// <param name="mean">Desired expected value.</param> /// <param name="meanLog">Desired expected logarithm.</param> /// <param name="power">Desired power.</param> /// <returns>A new GammaPower distribution.</returns> /// <remarks>This function is equivalent to maximum-likelihood estimation of a Gamma distribution /// from data given by sufficient statistics. /// This function is significantly slower than the other constructors since it /// involves nonlinear optimization. The algorithm is a generalized Newton iteration, /// described in "Estimating a Gamma distribution" by T. Minka, 2002. /// </remarks> public static GammaPower FromMeanAndMeanLog(double mean, double meanLog, double power) { // Constraints: // mean = Gamma(Shape + power)/Gamma(Shape)/Rate^power // meanLog = power*(digamma(Shape) - log(Rate)) // digamma(Shape) =approx log(Shape - 0.5) double logMeanOverPower = Math.Log(mean) / power; double meanLogOverPower = meanLog / power; double shape = 1; double logRate = 0; for (int iter = 0; iter < 1000; iter++) { double oldLogRate = logRate; double oldShape = shape; logRate = MMath.RisingFactorialLnOverN(shape, power) - logMeanOverPower; shape = Math.Exp(meanLogOverPower + logRate) + 0.5; //Console.WriteLine($"shape = {shape:g17}, logRate = {logRate:g17}"); if (MMath.AreEqual(oldLogRate, logRate) && MMath.AreEqual(oldShape, shape)) { break; } if (double.IsNaN(shape)) { throw new Exception("Failed to converge"); } } return(FromShapeAndRate(shape, Math.Exp(logRate), power)); }
/// <summary> /// Returns the value x such that GetProbLessThan(x) == probability. /// </summary> /// <param name="probability">A real number in [0,1].</param> /// <returns></returns> public double GetQuantile(double probability) { if (probability < 0) { throw new ArgumentOutOfRangeException("probability < 0"); } if (probability > 1) { throw new ArgumentOutOfRangeException("probability > 1"); } if (this.IsPointMass) { return((probability == 1.0) ? MMath.NextDouble(this.Point) : this.Point); } else if (!IsProper()) { throw new ImproperDistributionException(this); } else if (MMath.AreEqual(probability, 0)) { return(0); } else if (MMath.AreEqual(probability, 1)) { return(double.PositiveInfinity); } else if (Shape == 1) { // cdf is 1 - exp(-x*rate) return(-Math.Log(1 - probability) / Rate); } else { // Binary search double lowerBound = 0; double upperBound = double.MaxValue; while (lowerBound < upperBound) { double average = MMath.Average(lowerBound, upperBound); double p = GetProbLessThan(average); if (p == probability) { return(average); } else if (p < probability) { lowerBound = MMath.NextDouble(average); } else { upperBound = MMath.PreviousDouble(average); } } return(lowerBound); } }
// requires x < 0, r <= 0, and x-r*y <= 0 (or equivalently y < -x). public static double NormalCdfConFrac3(double x, double y, double r) { if (x > 0) { throw new ArgumentException("x >= 0"); } if (r > 0) { throw new ArgumentException("r > 0"); } if (x - r * y > 0) { throw new ArgumentException("x - r*y > 0"); } double numer = NormalCdfDx(x, y, r) + r * NormalCdfMomentDy(0, x, y, r); double numerPrev = 0; double denom = x; double denomPrev = 1; double rprev = 0; for (int i = 1; i < 1000; i++) { double numerNew = x * numer + i * numerPrev + r * NormalCdfMomentDy(i, x, y, r); double denomNew = x * denom + i * denomPrev; numerPrev = numer; numer = numerNew; denomPrev = denom; denom = denomNew; if (i % 2 == 1) { //Console.WriteLine("denom/dfact = {0}", denom / dfact); double result = -numer / denom; Console.WriteLine("iter {0}: {1}", i, result); if (double.IsInfinity(result) || double.IsNaN(result)) { throw new Exception(); } if (MMath.AreEqual(result, rprev)) { return(result); } rprev = result; } } throw new Exception(string.Format("NormalCdfConFrac3 not converging for x={0} y={1} r={2}", x, y, r)); }
public static Gaussian XAverageConditional_Helper([SkipIfUniform] Bernoulli isPositive, [SkipIfUniform, Proper] Gaussian x, bool forceProper) { if (x.IsPointMass) { if (isPositive.IsPointMass && (isPositive.Point != (x.Point > 0))) { return(Gaussian.PointMass(0)); } else { return(Gaussian.Uniform()); } } double tau = x.MeanTimesPrecision; double prec = x.Precision; if (prec == 0.0) { if (isPositive.IsPointMass) { if ((isPositive.Point && tau < 0) || (!isPositive.Point && tau > 0)) { // posterior is proportional to I(x>0) exp(tau*x) //double mp = -1 / tau; //double vp = mp * mp; return(Gaussian.FromNatural(-tau, tau * tau) / x); } } return(Gaussian.Uniform()); } else if (prec < 0) { throw new ImproperMessageException(x); } double sqrtPrec = Math.Sqrt(prec); // m/sqrt(v) = (m/v)/sqrt(1/v) double z = tau / sqrtPrec; // epsilon = p(b=F) // eq (51) in EP quickref double alpha; if (isPositive.IsPointMass) { if ((isPositive.Point && z < -10) || (!isPositive.Point && z > 10)) { if (z > 10) { z = -z; } //double Y = MMath.NormalCdfRatio(z); // dY = z*Y + 1 // d2Y = z*dY + Y // posterior mean = m + sqrt(v)/Y = sqrt(v)*(z + 1/Y) = sqrt(v)*dY/Y // = sqrt(v)*(d2Y/Y - 1)/z = (d2Y/Y - 1)/tau // =approx sqrt(v)*(-1/z) = -1/tau // posterior variance = v - v*dY/Y^2 =approx v/z^2 // posterior E[x^2] = v - v*dY/Y^2 + v*dY^2/Y^2 = v - v*dY/Y^2*(1 - dY) = v + v*z*dY/Y = v*d2Y/Y // d3Y = z*d2Y + 2*dY // = z*(z*dY + Y) + 2*dY // = z^2*dY + z*Y + 2*dY // = z^2*dY + 3*dY - 1 double d3Y = 6 * MMath.NormalCdfMomentRatio(3, z); //double dY = MMath.NormalCdfMomentRatio(1, z); double dY = (d3Y + 1) / (z * z + 3); if (MMath.AreEqual(dY, 0)) { double tau2 = tau * tau; if (tau2 > double.MaxValue) { return(Gaussian.PointMass(-1 / tau)); } else { return(Gaussian.FromNatural(-2 * tau, tau2)); } } // Y = (dY-1)/z // alpha = sqrtPrec*z/(dY-1) = tau/(dY-1) // alpha+tau = tau*(1 + 1/(dY-1)) = tau*dY/(dY-1) = alpha*dY // beta = alpha * (alpha + tau) // = alpha * tau * dY / (dY - 1) // = prec * z^2 * dY/(dY-1)^2 // prec/beta = (dY-1)^2/(dY*z^2) // prec/beta - 1 = ((dY-1)^2 - dY*z^2)/(dY*z^2) // weight = beta/(prec - beta) // = dY*z^2/((dY-1)^2 - dY*z^2) // = dY*z^2/(dY^2 -2*dY + 1 - dY*z^2) // = dY*z^2/(dY^2 + 1 + z*Y - d3Y) // = dY*z^2/(dY^2 + dY - d3Y) // = z^2/(dY + 1 - d3Y/dY) double d3YidY = d3Y / dY; double denom = dY - d3YidY + 1; double msgPrec = tau * tau / denom; // weight * (tau + alpha) + alpha // = z^2/(dY + 1 - d3Y/dY) * alpha*dY + alpha // = alpha*(z^2*dY/(dY + 1 - d3Y/dY) + 1) // = alpha*(z^2*dY + dY + 1 - d3Y/dY)/(dY + 1 - d3Y/dY) // = alpha*(z^2*dY + dY + 1 - d3Y/dY)/(dY + 1 - d3Y/dY) // = alpha*(d3Y - 2*dY + 2 - d3Y/dY)/(dY + 1 - d3Y/dY) // z^2*dY = d3Y - 3*dY + 1 double numer = (d3Y - 2 * dY - d3YidY + 2) / (dY - 1); double msgMeanTimesPrec = tau * numer / denom; if (msgPrec > double.MaxValue) { // In this case, the message should be the posterior. // posterior mean = (msgMeanTimesPrec + tau)*denom/(tau*tau) // = (numer + denom)/tau return(Gaussian.PointMass((numer + denom) / tau)); } else { return(Gaussian.FromNatural(msgMeanTimesPrec, msgPrec)); } } else if (isPositive.Point) { alpha = sqrtPrec / MMath.NormalCdfRatio(z); } else { alpha = -sqrtPrec / MMath.NormalCdfRatio(-z); } } else { //double v = MMath.LogSumExp(isPositive.LogProbTrue + MMath.NormalCdfLn(z), isPositive.LogProbFalse + MMath.NormalCdfLn(-z)); double v = LogAverageFactor(isPositive, x); alpha = sqrtPrec * Math.Exp(-z * z * 0.5 - MMath.LnSqrt2PI - v) * (2 * isPositive.GetProbTrue() - 1); } // eq (52) in EP quickref (where tau = mnoti/Vnoti) double beta; if (alpha == 0) { beta = 0; // avoid 0 * infinity } else { beta = alpha * (alpha + tau); } double weight = beta / (prec - beta); if (forceProper && weight < 0) { weight = 0; } Gaussian result = new Gaussian(); if (weight == 0) { // avoid 0 * infinity result.MeanTimesPrecision = alpha; } else { // eq (31) in EP quickref; same as inv(inv(beta)-inv(prec)) result.Precision = prec * weight; // eq (30) in EP quickref times above and simplified result.MeanTimesPrecision = weight * (tau + alpha) + alpha; } if (double.IsNaN(result.Precision) || double.IsNaN(result.MeanTimesPrecision)) { throw new InferRuntimeException($"result is NaN. isPositive={isPositive}, x={x}, forceProper={forceProper}"); } return(result); }