// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // // Copyright (c) 2009-2010 Math.NET // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // // Cephes Math Library, Stephen L. Moshier // ALGLIB 2.0.1, Sergey Bochkanov // using System; // ReSharper disable CheckNamespace namespace IStation.Numerics // ReSharper restore CheckNamespace { public static partial class SpecialFunctions { /// /// The order of the approximation. /// const int GammaN = 10; /// /// Auxiliary variable when evaluating the function. /// const double GammaR = 10.900511; /// /// Polynomial coefficients for the approximation. /// static readonly double[] GammaDk = { 2.48574089138753565546e-5, 1.05142378581721974210, -3.45687097222016235469, 4.51227709466894823700, -2.98285225323576655721, 1.05639711577126713077, -1.95428773191645869583e-1, 1.70970543404441224307e-2, -5.71926117404305781283e-4, 4.63399473359905636708e-6, -2.71994908488607703910e-9 }; /// /// Computes the logarithm of the Gamma function. /// /// The argument of the gamma function. /// The logarithm of the gamma function. /// /// This implementation of the computation of the gamma and logarithm of the gamma function follows the derivation in /// "An Analysis Of The Lanczos Gamma Approximation", Glendon Ralph Pugh, 2004. /// We use the implementation listed on p. 116 which achieves an accuracy of 16 floating point digits. Although 16 digit accuracy /// should be sufficient for double values, improving accuracy is possible (see p. 126 in Pugh). /// Our unit tests suggest that the accuracy of the Gamma function is correct up to 14 floating point digits. /// public static double GammaLn(double z) { if (z < 0.5) { double s = GammaDk[0]; for (int i = 1; i <= GammaN; i++) { s += GammaDk[i]/(i - z); } return Constants.LnPi - Math.Log(Math.Sin(Math.PI*z)) - Math.Log(s) - Constants.LogTwoSqrtEOverPi - ((0.5 - z)*Math.Log((0.5 - z + GammaR)/Math.E)); } else { double s = GammaDk[0]; for (int i = 1; i <= GammaN; i++) { s += GammaDk[i]/(z + i - 1.0); } return Math.Log(s) + Constants.LogTwoSqrtEOverPi + ((z - 0.5)*Math.Log((z - 0.5 + GammaR)/Math.E)); } } /// /// Computes the Gamma function. /// /// The argument of the gamma function. /// The logarithm of the gamma function. /// /// /// This implementation of the computation of the gamma and logarithm of the gamma function follows the derivation in /// "An Analysis Of The Lanczos Gamma Approximation", Glendon Ralph Pugh, 2004. /// We use the implementation listed on p. 116 which should achieve an accuracy of 16 floating point digits. Although 16 digit accuracy /// should be sufficient for double values, improving accuracy is possible (see p. 126 in Pugh). /// /// Our unit tests suggest that the accuracy of the Gamma function is correct up to 13 floating point digits. /// public static double Gamma(double z) { if (z < 0.5) { double s = GammaDk[0]; for (int i = 1; i <= GammaN; i++) { s += GammaDk[i]/(i - z); } return Math.PI/(Math.Sin(Math.PI*z) *s *Constants.TwoSqrtEOverPi *Math.Pow((0.5 - z + GammaR)/Math.E, 0.5 - z)); } else { double s = GammaDk[0]; for (int i = 1; i <= GammaN; i++) { s += GammaDk[i]/(z + i - 1.0); } return s*Constants.TwoSqrtEOverPi*Math.Pow((z - 0.5 + GammaR)/Math.E, z - 0.5); } } /// /// Returns the upper incomplete regularized gamma function /// Q(a,x) = 1/Gamma(a) * int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0. /// /// The argument for the gamma function. /// The lower integral limit. /// The upper incomplete regularized gamma function. public static double GammaUpperRegularized(double a, double x) { const double epsilon = 0.000000000000001; const double big = 4503599627370496.0; const double bigInv = 2.22044604925031308085e-16; if (x < 1d || x <= a) { return 1d - GammaLowerRegularized(a, x); } double ax = a*Math.Log(x) - x - GammaLn(a); if (ax < -709.78271289338399) { return a < x ? 0d : 1d; } ax = Math.Exp(ax); double t; double y = 1 - a; double z = x + y + 1; double c = 0; double pkm2 = 1; double qkm2 = x; double pkm1 = x + 1; double qkm1 = z*x; double ans = pkm1/qkm1; do { c = c + 1; y = y + 1; z = z + 2; double yc = y*c; double pk = pkm1*z - pkm2*yc; double qk = qkm1*z - qkm2*yc; if (qk != 0) { double r = pk/qk; t = Math.Abs((ans - r)/r); ans = r; } else { t = 1; } pkm2 = pkm1; pkm1 = pk; qkm2 = qkm1; qkm1 = qk; if (Math.Abs(pk) > big) { pkm2 = pkm2*bigInv; pkm1 = pkm1*bigInv; qkm2 = qkm2*bigInv; qkm1 = qkm1*bigInv; } } while (t > epsilon); return ans*ax; } /// /// Returns the upper incomplete gamma function /// Gamma(a,x) = int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0. /// /// The argument for the gamma function. /// The lower integral limit. /// The upper incomplete gamma function. public static double GammaUpperIncomplete(double a, double x) { return GammaUpperRegularized(a, x)*Gamma(a); } /// /// Returns the lower incomplete gamma function /// gamma(a,x) = int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0. /// /// The argument for the gamma function. /// The upper integral limit. /// The lower incomplete gamma function. public static double GammaLowerIncomplete(double a, double x) { return GammaLowerRegularized(a, x)*Gamma(a); } /// /// Returns the lower incomplete regularized gamma function /// P(a,x) = 1/Gamma(a) * int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0. /// /// The argument for the gamma function. /// The upper integral limit. /// The lower incomplete gamma function. public static double GammaLowerRegularized(double a, double x) { const double epsilon = 0.000000000000001; const double big = 4503599627370496.0; const double bigInv = 2.22044604925031308085e-16; if (a < 0d) { throw new ArgumentOutOfRangeException(nameof(a), "Value must not be negative (zero is ok)."); } if (x < 0d) { throw new ArgumentOutOfRangeException(nameof(x), "Value must not be negative (zero is ok)."); } if (a.AlmostEqual(0.0)) { if (x.AlmostEqual(0.0)) { //use right hand limit value because so that regularized upper/lower gamma definition holds. return 1d; } return 1d; } if (x.AlmostEqual(0.0)) { return 0d; } double ax = (a*Math.Log(x)) - x - GammaLn(a); if (ax < -709.78271289338399) { return a < x ? 1d : 0d; } if (x <= 1 || x <= a) { double r2 = a; double c2 = 1; double ans2 = 1; do { r2 = r2 + 1; c2 = c2*x/r2; ans2 += c2; } while ((c2/ans2) > epsilon); return Math.Exp(ax)*ans2/a; } int c = 0; double y = 1 - a; double z = x + y + 1; double p3 = 1; double q3 = x; double p2 = x + 1; double q2 = z*x; double ans = p2/q2; double error; do { c++; y += 1; z += 2; double yc = y*c; double p = (p2*z) - (p3*yc); double q = (q2*z) - (q3*yc); if (q != 0) { double nextans = p/q; error = Math.Abs((ans - nextans)/nextans); ans = nextans; } else { // zero div, skip error = 1; } // shift p3 = p2; p2 = p; q3 = q2; q2 = q; // normalize fraction when the numerator becomes large if (Math.Abs(p) > big) { p3 *= bigInv; p2 *= bigInv; q3 *= bigInv; q2 *= bigInv; } } while (error > epsilon); return 1d - (Math.Exp(ax)*ans); } /// /// Returns the inverse P^(-1) of the regularized lower incomplete gamma function /// P(a,x) = 1/Gamma(a) * int(exp(-t)t^(a-1),t=0..x) for real a > 0, x > 0, /// such that P^(-1)(a,P(a,x)) == x. /// public static double GammaLowerRegularizedInv(double a, double y0) { const double epsilon = 0.000000000000001; const double big = 4503599627370496.0; const double threshold = 5*epsilon; if (double.IsNaN(a) || double.IsNaN(y0)) { return double.NaN; } if (a < 0 || a.AlmostEqual(0.0)) { throw new ArgumentOutOfRangeException(nameof(a)); } if (y0 < 0 || y0 > 1) { throw new ArgumentOutOfRangeException(nameof(y0)); } if (y0.AlmostEqual(0.0)) { return 0d; } if (y0.AlmostEqual(1.0)) { return double.PositiveInfinity; } y0 = 1 - y0; double xUpper = big; double xLower = 0; double yUpper = 1; double yLower = 0; // Initial Guess double d = 1/(9*a); double y = 1 - d - (0.98*Constants.Sqrt2*ErfInv((2.0*y0) - 1.0)*Math.Sqrt(d)); double x = a*y*y*y; double lgm = GammaLn(a); for (int i = 0; i < 20; i++) { if (x < xLower || x > xUpper) { d = 0.0625; break; } y = 1 - GammaLowerRegularized(a, x); if (y < yLower || y > yUpper) { d = 0.0625; break; } if (y < y0) { xUpper = x; yLower = y; } else { xLower = x; yUpper = y; } d = ((a - 1)*Math.Log(x)) - x - lgm; if (d < -709.78271289338399) { d = 0.0625; break; } d = -Math.Exp(d); d = (y - y0)/d; if (Math.Abs(d/x) < epsilon) { return x; } if ((d > (x/4)) && (y0 < 0.05)) { // Naive heuristics for cases near the singularity d = x/10; } x -= d; } if (xUpper == big) { if (x <= 0) { x = 1; } while (xUpper == big) { x = (1 + d)*x; y = 1 - GammaLowerRegularized(a, x); if (y < y0) { xUpper = x; yLower = y; break; } d = d + d; } } int dir = 0; d = 0.5; for (int i = 0; i < 400; i++) { x = xLower + (d*(xUpper - xLower)); y = 1 - GammaLowerRegularized(a, x); lgm = (xUpper - xLower)/(xLower + xUpper); if (Math.Abs(lgm) < threshold) { return x; } lgm = (y - y0)/y0; if (Math.Abs(lgm) < threshold) { return x; } if (x <= 0d) { return 0d; } if (y >= y0) { xLower = x; yUpper = y; if (dir < 0) { dir = 0; d = 0.5; } else { if (dir > 1) { d = (0.5*d) + 0.5; } else { d = (y0 - yLower)/(yUpper - yLower); } } dir = dir + 1; } else { xUpper = x; yLower = y; if (dir > 0) { dir = 0; d = 0.5; } else { if (dir < -1) { d = 0.5*d; } else { d = (y0 - yLower)/(yUpper - yLower); } } dir = dir - 1; } } return x; } /// /// Computes the Digamma function which is mathematically defined as the derivative of the logarithm of the gamma function. /// This implementation is based on /// Jose Bernardo /// Algorithm AS 103: /// Psi ( Digamma ) Function, /// Applied Statistics, /// Volume 25, Number 3, 1976, pages 315-317. /// Using the modifications as in Tom Minka's lightspeed toolbox. /// /// The argument of the digamma function. /// The value of the DiGamma function at . public static double DiGamma(double x) { const double c = 12.0; const double d1 = -0.57721566490153286; const double d2 = 1.6449340668482264365; const double s = 1e-6; const double s3 = 1.0/12.0; const double s4 = 1.0/120.0; const double s5 = 1.0/252.0; const double s6 = 1.0/240.0; const double s7 = 1.0/132.0; if (double.IsNegativeInfinity(x) || double.IsNaN(x)) { return double.NaN; } // Handle special cases. if (x <= 0 && Math.Floor(x) == x) { return double.NegativeInfinity; } // Use inversion formula for negative numbers. if (x < 0) { return DiGamma(1.0 - x) + (Math.PI/Math.Tan(-Math.PI*x)); } if (x <= s) { return d1 - (1/x) + (d2*x); } double result = 0; while (x < c) { result -= 1/x; x++; } if (x >= c) { var r = 1/x; result += Math.Log(x) - (0.5*r); r *= r; result -= r*(s3 - (r*(s4 - (r*(s5 - (r*(s6 - (r*s7)))))))); } return result; } /// /// Computes the inverse Digamma function: this is the inverse of the logarithm of the gamma function. This function will /// only return solutions that are positive. /// This implementation is based on the bisection method. /// /// The argument of the inverse digamma function. /// The positive solution to the inverse DiGamma function at . public static double DiGammaInv(double p) { if (double.IsNaN(p)) { return double.NaN; } if (double.IsNegativeInfinity(p)) { return 0.0; } if (double.IsPositiveInfinity(p)) { return double.PositiveInfinity; } var x = Math.Exp(p); for (var d = 1.0; d > 1.0e-15; d /= 2.0) { x += d*Math.Sign(p - DiGamma(x)); } return x; } } }