/// <summary> /// Returns the Laczos approximation of Γ(z) /// </summary> public static double Tgamma(double x) { Debug.Assert(x > 0); // check for large x to avoid potential Inf/Inf below if (x > DoubleLimits.MaxGamma + 1) { return(double.PositiveInfinity); } // use the Lanczos approximation double xgh = x + (Lanczos.G - 0.5); double result = Lanczos.Series(x); if (x > MaxPowerTermX) { // we're going to overflow unless this is done with care: double hp = Math.Pow(xgh, (x / 2) - 0.25); result *= (hp / Math.Exp(xgh)); result *= hp; } else { result *= (Math.Pow(xgh, x - 0.5) / Math.Exp(xgh)); } return(result); }
/// <summary> /// Returns ln(|Γ(1+dz)|) using the Lanczos approzimation /// </summary> public static double Lgamma1p(double dz) { double result = dz * Math.Log((dz + (Lanczos.G + 0.5)) / Math.E); result += Math2.Log1p(dz / (Lanczos.G + 0.5)) / 2; result += Math2.Log1p(Lanczos.Sum_near_1(dz)); return(result); }
/// <summary> /// Returns Γ(z)/Γ(z+delta) using the Lanczos approximation /// </summary> public static double TgammaDeltaRatio(double x, double delta) { Debug.Assert(x > 0 && delta + x > 0); // Compute Γ(x)/Γ(x+Δ), which reduces to: // // ((x+g-0.5)/(x+Δ+g-0.5))^(x-0.5) * (x+Δ+g-0.5)^-Δ * e^Δ * (Sum(x)/Sum(x+Δ)) // = (1+Δ/(x+g-0.5))^-(x-0.5) * (x+Δ+g-0.5)^-Δ * e^Δ * (Sum(x)/Sum(x+Δ)) double xgh = x + (Lanczos.G - 0.5); double xdgh = x + delta + (Lanczos.G - 0.5); double mu = delta / xgh; double result = Lanczos.Series(x) / Lanczos.Series(x + delta); result *= (Math.Abs(mu) < 0.5) ? Math.Exp((0.5 - x) * Math2.Log1p(mu)) : Math.Pow(xgh / xdgh, x - 0.5); result *= Math.Pow(Math.E / xdgh, delta); return(result); }
/// <summary> /// Compute the leading power terms in the incomplete Beta: /// <para>(x^a)(y^b)/Beta(a,b) when regularized</para> /// <para>(x^a)(y^b) otherwise</para> /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <param name="x"></param> /// <param name="y">1-x</param> /// <param name="normalised"></param> /// <param name="multiplier"></param> /// <returns></returns> /// <remarks> /// Almost all of the error in the incomplete beta comes from this /// function: particularly when a and b are large. Computing large /// powers are *hard* though, and using logarithms just leads to /// horrendous cancellation errors. /// </remarks> public static double PowerTerms(double a, double b, double x, double y, bool normalised, double multiplier) { if (multiplier == 0) { return(0); } if (!normalised) { return(multiplier * (Math.Pow(x, a) * Math.Pow(y, b))); } double c = a + b; // combine power terms with Lanczos approximation: double agh = a + (Lanczos.G - 0.5); double bgh = b + (Lanczos.G - 0.5); double cgh = c + (Lanczos.G - 0.5); // compute: // mutiplier * (x*cgh/agh)^a * (y*cgh/bgh)^b * Sqrt((agh*bgh)/(e*cgh)) * (Sum_expG_scaled(c) / (Sum_expG_scaled(a) * Sum_expG_scaled(b))) double factor = (Lanczos.SeriesExpGScaled(c) / Lanczos.SeriesExpGScaled(a)) / Lanczos.SeriesExpGScaled(b); if (a > b) { factor *= Math.Sqrt((bgh / Math.E) * (agh / cgh)); } else { factor *= Math.Sqrt((agh / Math.E) * (bgh / cgh)); } if (double.IsInfinity(factor * multiplier)) { #if EXTRA_DEBUG Debug.WriteLine("Using Logs in _Beta.PowerTerms: a = {0}; b = {1}; x = {2}; y= {3}; mult = {4}", a, b, x, y, multiplier); #endif // this will probably fail but... double logValue; if (x <= y) { logValue = a * Math.Log(x) + b * Math2.Log1p(-x); } else { logValue = a * Math2.Log1p(-y) + b * Math.Log(y); } logValue += Math.Log(multiplier); logValue -= Math2.LogBeta(a, b); return(Math.Exp(logValue)); } else { factor *= multiplier; } double result = 1; //Debug.WriteLine("IbetaPowerTerms(a: {0}, b: {1}, x: {2}, y: {3}) - Prefix = {4}", a, b, x, y, result); // l1 and l2 are the base of the exponents minus one: double l1 = (x * b - y * agh) / agh; double l2 = (y * a - x * bgh) / bgh; // t1 and t2 are the exponent terms double t1 = (x * cgh) / agh; double t2 = (y * cgh) / bgh; // if possible, set the result to partially cancel out with the first term bool sameDirection = false; if (t1 >= 1 && t2 >= 1) { sameDirection = true; if (factor < 1) { result = factor; factor = 1; } } else if (t1 <= 1 && t2 <= 1) { sameDirection = true; if (factor > 1) { result = factor; factor = 1; } } if (sameDirection) { // This first branch handles the simple case where the two power terms // both go in the same direction (towards zero or towards infinity). // In this case if either term overflows or underflows, // then the product of the two must do so also. if (Math.Abs(l1) < 0.5) { result *= Math.Exp(a * Math2.Log1p(l1)); } else { result *= Math.Pow(t1, a); } if (Math.Abs(l2) < 0.5) { result *= Math.Exp(b * Math2.Log1p(l2)); } else { result *= Math.Pow(t2, b); } result *= factor; } else { // This second branch handles the case where the two power terms // go in opposite directions (towards zero or towards infinity). Debug.Assert(result == 1); bool useExpT1 = false; bool useExpT2 = false; double logt1; if (Math.Abs(l1) < 0.5) { useExpT1 = true; logt1 = a * Math2.Log1p(l1); } else { logt1 = a * Math.Log(t1); } double logt2; if (Math.Abs(l2) < 0.5) { useExpT2 = true; logt2 = b * Math2.Log1p(l2); } else { logt2 = b * Math.Log(t2); } if ((logt1 >= DoubleLimits.MaxLogValue) || (logt1 <= DoubleLimits.MinLogValue) || (logt2 >= DoubleLimits.MaxLogValue) || (logt2 <= DoubleLimits.MinLogValue) ) { double logSum = logt1 + logt2; if ((logSum >= DoubleLimits.MaxLogValue) || (logSum <= DoubleLimits.MinLogValue)) { result = Math.Exp(logSum + Math.Log(factor)); } else { result = factor * Math.Exp(logSum); } } else { // ensure that t1 and result will partially cancel if (t1 >= 1) { if (factor < 1) { result = factor; factor = 1; } } else { if (factor > 1) { result = factor; factor = 1; } } if (useExpT1) { result *= Math.Exp(logt1); } else { result *= Math.Pow(t1, a); } if (useExpT2) { result *= Math.Exp(logt2); } else { result *= Math.Pow(t2, b); } result *= factor; } } return(result); }
/// <summary> /// Compute: multiplier * x^a/Beta(a,b) while trying to avoid overflows/underflows /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="multiplier"></param> /// <returns></returns> static double SeriesRegularizedPrefix(double a, double b, double x, double y, double multiplier) { double c = a + b; // incomplete beta power term, combined with the Lanczos approximation: double agh = a + (Lanczos.G - 0.5); double bgh = b + (Lanczos.G - 0.5); double cgh = c + (Lanczos.G - 0.5); // compute: // mutiplier * (x*cgh/agh)^a * (cgh/bgh)^b * Sqrt((agh*bgh)/(e*cgh)) * (SumGScaled(c)/(SumGScaled(a)*SumGScaled(b)) double factor = (Lanczos.SeriesExpGScaled(c) / Lanczos.SeriesExpGScaled(a)) / Lanczos.SeriesExpGScaled(b); if (a > b) { factor *= Math.Sqrt((bgh / Math.E) * (agh / cgh)); } else { factor *= Math.Sqrt((agh / Math.E) * (bgh / cgh)); } factor *= multiplier; double result = 1; // l1 and l2 are the base of the exponents minus one: double l1 = (x * b - y * agh) / agh; double l2 = a / bgh; // t1 and t2 are the exponent terms double t1 = (x * cgh) / agh; double t2 = cgh / bgh; // if possible, set the result to partially cancel out with the first term Debug.Assert(t2 >= 1); if (t1 >= 1) { // This first branch handles the simple case where the two power terms // both go in the same direction (towards zero or towards infinity). // In this case if either term overflows or underflows, // then the product of the two must do so also. if (factor < 1) { result = factor; factor = 1; } if (Math.Abs(l1) < 0.5) { result *= Math.Exp(a * Math2.Log1p(l1)); } else { result *= Math.Pow(t1, a); } if (Math.Abs(l2) < 0.5) { result *= Math.Exp(b * Math2.Log1p(l2)); } else { result *= Math.Pow(t2, b); } result *= factor; } else { // This second branch handles the case where the two power terms // go in opposite directions (towards zero or towards infinity). Debug.Assert(result == 1); Debug.Assert(t1 < 1); bool useExpT1 = false; bool useExpT2 = false; double logt1; if (Math.Abs(l1) < 0.5) { useExpT1 = true; logt1 = a * Math2.Log1p(l1); } else { logt1 = a * Math.Log(t1); } double logt2; if (Math.Abs(l2) < 0.5) { useExpT2 = true; logt2 = b * Math2.Log1p(l2); } else { logt2 = b * Math.Log(t2); } if ((logt1 >= DoubleLimits.MaxLogValue) || (logt1 <= DoubleLimits.MinLogValue) || (logt2 >= DoubleLimits.MaxLogValue) || (logt2 <= DoubleLimits.MinLogValue) ) { double logSum = logt1 + logt2; if ((logSum >= DoubleLimits.MaxLogValue) || (logSum <= DoubleLimits.MinLogValue)) { result = Math.Exp(logSum + Math.Log(factor)); } else { result = factor * Math.Exp(logSum); } } else { // Set result so that t1 and result will partially cancel if (factor > 1) { result = factor; factor = 1; } if (useExpT1) { result *= Math.Exp(logt1); } else { result *= Math.Pow(t1, a); } if (useExpT2) { result *= Math.Exp(logt2); } else { result *= Math.Pow(t2, b); } result *= factor; } } return(result); }
/// <summary> /// Returns Log(Beta(a,b)) /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="b">Requires finite b > 0</param> public static double LogBeta(double a, double b) { double c = a + b; if ((!(a > 0) || double.IsInfinity(a)) || (!(b > 0) || double.IsInfinity(b))) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite a,b > 0", a, b); return double.NaN; } if (double.IsInfinity(c)) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite c == a+b: c = {2}", a, b, c); return double.NaN; } // Special cases: if ((c == a) && (b < DoubleLimits.MachineEpsilon)) return Math2.Lgamma(b); if ((c == b) && (a < DoubleLimits.MachineEpsilon)) return Math2.Lgamma(a); if (b == 1) return -Math.Log(a); if (a == 1) return -Math.Log(b); // B(a,b) == B(b, a) if (a < b) Utility.Swap(ref a, ref b); // from this point a >= b if (a < 1) { // When x < TinyX, Γ(x) ~ 1/x const double SmallX = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if (a < SmallX) { if (c < SmallX) { // In this range, Beta(a, b) ~= 1/a + 1/b // so, we won't have an argument overflow as long as // the min(a, b) >= 2*DoubleLimits.MinNormalValue if (b < 2 * DoubleLimits.MinNormalValue) return Math.Log(c / a) - Math.Log(b); return Math.Log((c / a) / b); } Debug.Assert(c <= GammaSmall.UpperLimit); return -Math.Log((GammaSmall.Tgamma(c) * a * b)); } if (b < SmallX) return Math.Log(Tgamma(a) / (b * Tgamma(c))); return Math.Log((Tgamma(a) / Tgamma(c)) * Tgamma(b)); } else if (a >= StirlingGamma.LowerLimit) { if ( b >= StirlingGamma.LowerLimit ) return StirlingGamma.LogBeta(a, b); return StirlingGamma.LgammaDelta(a, b) + Lgamma(b); } return Lanczos.LogBeta(a, b); }
/// <summary> /// Returns the Beta function /// <para>B(a,b) = Γ(a)*Γ(b)/Γ(a+b)</para> /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="b">Requires finite b > 0</param> public static double Beta(double a, double b) { double c = a + b; if ((!(a > 0) || double.IsInfinity(a)) || (!(b > 0) || double.IsInfinity(b))) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite a,b > 0", a, b); return double.NaN; } if (double.IsInfinity(c)) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite c == a+b: c = {2}", a, b, c); return double.NaN; } // Special cases: if ((c == a) && (b < DoubleLimits.MachineEpsilon)) return Math2.Tgamma(b); if ((c == b) && (a < DoubleLimits.MachineEpsilon)) return Math2.Tgamma(a); if (b == 1) return 1 / a; if (a == 1) return 1 / b; // B(a,b) == B(b, a) if (a < b) Utility.Swap(ref a, ref b); // from this point a >= b if (a < 1) { // When x < TinyX, Γ(x) ~ 1/x const double SmallX = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if ( a < SmallX ) { if (c < SmallX) return (c / a) / b; Debug.Assert(c <= GammaSmall.UpperLimit); return 1/(GammaSmall.Tgamma(c) * a * b); } if ( b < SmallX ) return Tgamma(a) / (b * Tgamma(c)); return Tgamma(a) * ( Tgamma(b)/Tgamma(c) ); } // Our Stirling series is more accurate than Lanczos if (a >= StirlingGamma.LowerLimit ) { if (b >= StirlingGamma.LowerLimit) return StirlingGamma.Beta(a, b); // for large a, Γ(a)/Γ(a + b) ~ a^-b, so don't underflow too soon if (b < 1 || b * Math.Log(a) <= -DoubleLimits.MinLogValue) return Tgamma(b) * StirlingGamma.TgammaDeltaRatio(a, b); // fall through for very large a small 1 < b < LowerLimit } return Lanczos.Beta(a, b); }
/// <summary> /// Returns the ratio of gamma functions /// <para>TgammaDeltaRatio(x,delta) = Γ(x)/Γ(x+delta)</para> /// </summary> /// <param name="x">The argument for the gamma function in the numerator. Requires x > 0</param> /// <param name="delta">The offset for the denominator. Requires x + delta > 0</param> public static double TgammaDeltaRatio(double x, double delta) { double xpd = x + delta; if (double.IsNaN(x) || double.IsNaN(delta)) { Policies.ReportDomainError("TgammaDeltaRatio(x: {0}, delta: {1}): Requires x > 0; x + delta > 0", x, delta); return double.NaN; } // Γ(x)/Γ(x) == 1 // this needs to be here in case x = 0 if (delta == 0) return 1; if (!(x > 0)) { Policies.ReportDomainError("TgammaDeltaRatio(x: {0}, delta: {1}): Requires x > 0; negative x not implemented", x, delta); return double.NaN; } if (!(xpd > 0)) { Policies.ReportDomainError("TgammaDeltaRatio(x: {0}, delta: {1}): Requires x+delta > 0", x, delta); return double.NaN; } if (double.IsInfinity(x)) { if (double.IsInfinity(delta)) { Policies.ReportDomainError("TgammaDeltaRatio(x: {0}, delta: {1}): Infinity/Infinity", x, delta); return double.NaN; } return double.PositiveInfinity; } if (double.IsInfinity(delta)) return 0.0; if (IsInteger(delta)) { if (IsInteger(x)) { // Both x and delta are integers, see if we can just use table lookup // of the factorials to get the result: if ((x <= Math2.FactorialTable.Length) && (x + delta <= Math2.FactorialTable.Length)) return Math2.FactorialTable[(int)x - 1] / Math2.FactorialTable[(int)(x + delta) - 1]; } // if delta is a small integer, we can use a finite product if (Math.Abs(delta) < 20) { if (delta == 0) return 1; if (delta < 0) { x -= 1; double result = x; while ( ++delta < 0 ) { x -= 1; result *= x; } return result; } else { double result = x; while ( --delta > 0 ) { x += 1; result *= x; } return 1/result; } } } // our Tgamma ratio already handles cases where // one or both arguments are small const double LowerLimit = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; // 1.73eps if (x < LowerLimit || xpd < LowerLimit) return Math2.TgammaRatio(x, xpd); if ((x < 1 && delta > Math2.FactorialTable.Length) || (xpd < 1 && x > Math2.FactorialTable.Length)) return Math2.TgammaRatio(x, xpd); // Use the Lanczos approximation to compute the delta ratio return Lanczos.TgammaDeltaRatio(x, delta); }