internal static double PowerOverFactorial(double x, double nu) { if (nu < 16.0) { return(Math.Pow(x, nu) / AdvancedMath.Gamma(nu + 1.0)); } else { return(Stirling.PowerFactor(x, nu)); } }
// This function computes x^{\nu} / \Gamma(\nu + 1), which can easily become Infinity/Infinity=NaN for large \nu if computed naively. internal static double PowOverGammaPlusOne(double x, double nu) { if (nu < 16.0) { return(Math.Pow(x, nu) / AdvancedMath.Gamma(nu + 1.0)); } else { return(Stirling.PowOverGammaPlusOne(x, nu)); } }
private static SolutionPair Airy_Series(double x) { // compute k = 0 terms in f' and g' series, and in f and g series // compute terms to get k = 0 terms in a' and b' and a and b series double g = 1.0 / Math.Pow(3.0, 1.0 / 3.0) / AdvancedMath.Gamma(1.0 / 3.0); double ap = -g; double bp = g; double f = 1.0 / Math.Pow(3.0, 2.0 / 3.0) / AdvancedMath.Gamma(2.0 / 3.0); g *= x; double a = f - g; double b = f + g; // we will need to multiply by x^2 to produce higher terms, so remember it double x2 = x * x; for (int k = 1; k < Global.SeriesMax; k++) { // remember old values double a_old = a; double b_old = b; double ap_old = ap; double bp_old = bp; // compute 3k double tk = 3 * k; // kth term in f' and g' series, and corresponding a' and b' series f *= x2 / (tk - 1); g *= x2 / tk; ap += (f - g); bp += (f + g); // kth term in f and g series, and corresponding a and b series f *= x / tk; g *= x / (tk + 1); a += (f - g); b += (f + g); // check for convergence if ((a == a_old) && (b == b_old) && (ap == ap_old) && (bp == bp_old)) { return(new SolutionPair( a, ap, Global.SqrtThree * b, Global.SqrtThree * bp )); } } throw new NonconvergenceException(); }
/// <summary> /// Computes the generalized exponential integral. /// </summary> /// <param name="n">The order parameter.</param> /// <param name="x">The argument, which must be non-negative.</param> /// <returns>The value of E<sub>n</sub>(x).</returns> /// <remarks> /// <para>The generalized exponential integral is defined as:</para> /// <img src="../images/EIntegral.png" /> /// <para>It is related to the incomplete Gamma function (<see cref="Gamma(double, double)"/>) /// for negative, integer shape parameters.</para> /// <img src="../images/EnGammaRelation.png" /> /// <para>For n=1, it expressible as a simple power series.</para> /// <img src="../images/E1Series.png" /> /// <para>For negative x, E<sub>1</sub>(x) develops an imaginary part, but its real part is given by the Ei(x) function /// (<see cref="IntegralEi(double)"/>).</para> /// <img src="../images/E1EiRelation.png" /> /// <para>To compute E<sub>1</sub>(z) in the entire complex plane, use <see cref="AdvancedComplexMath.Ein(Complex)"/>.</para> /// <para>Sometimes the function E<sub>1</sub>(z) is called the exponential integral, and sometimes that name is used /// for Ei(x). In hydrology, E<sub>1</sub>(x) is sometimes called the Well function.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="x"/> is negative.</exception> /// <seealso href="http://mathworld.wolfram.com/En-Function.html"/> public static double IntegralE(int n, double x) { if (x < 0.0) { throw new ArgumentOutOfRangeException(nameof(x)); } // Special case x = 0. if (x == 0.0) { if (n <= 1) { return(Double.PositiveInfinity); } else { return(1.0 / (n - 1)); } } // Special case n negative and zero. if (n < 0) { // negative n is expressible using incomplete Gamma return(AdvancedMath.Gamma(1 - n, x) / MoreMath.Pow(x, 1 - n)); } else if (n == 0) { // special case n=0 return(Math.Exp(-x) / x); } // Now we are sure x > 0 and n > 0. if (x < 2.0) { return(IntegralE_Series(n, x)); } else if (x < expLimit) { // Since E_n(x) < e^{-x}, we can short-cut to zero if x is big enough. // This nicely avoids our continued fraction's bad behavior for infinite x. return(IntegralE_ContinuedFraction(n, x)); } else if (x <= Double.PositiveInfinity) { return(0.0); } else { Debug.Assert(Double.IsNaN(x)); return(x); } }
// series near the origin; this is entirely analogous to the Bessel series near the origin // it has a corresponding radius of rapid convergence, x < 4 + 2 Sqrt(nu) // This is exactly the same as BesselJ_Series with xx -> -xx. // We could even factor this out into a common method with an additional parameter. private static void ModifiedBesselI_Series(double nu, double x, out double I, out double IP) { if (x == 0.0) { if (nu == 0.0) { I = 1.0; IP = 0.0; } else if (nu < 1.0) { I = 0.0; IP = Double.PositiveInfinity; } else if (nu == 1.0) { I = 0.0; IP = 0.5; } else { I = 0.0; IP = 0.0; } } else { double x2 = x / 2.0; double xx = x2 * x2; double dI; if (nu < 128.0) { dI = Math.Pow(x2, nu) / AdvancedMath.Gamma(nu + 1.0); } else { dI = Math.Exp(nu * Math.Log(x2) - AdvancedMath.LogGamma(nu + 1.0)); } I = dI; IP = nu * dI; for (int k = 1; k < Global.SeriesMax; k++) { double I_old = I; double IP_old = IP; dI *= xx / k / (nu + k); I += dI; IP += (nu + 2 * k) * dI; if ((I == I_old) && (IP == IP_old)) { IP = IP / x; return; } } } }
// Li_n(x) = \sum_{k=0}^{\infty} \zeta(n-k) \frac{\log^k x}{k!} + \frac{\log^{n-1} x}{(n-1)!} \left( H_{n-1} -\log(-\log x) \right) private static double PolyLog_LogSeries(int n, double x) { double lnx = Math.Log(x); double f = AdvancedMath.RiemannZeta(n); if (lnx == 0.0) { return(f); } // c stores [log(x)]^k / k! double c = 1.0; for (int k = 1; k < Global.SeriesMax; k++) { double f_old = f; c *= lnx / k; // argument of zeta int m = n - k; if (m < 0) { // For negative arguments, use \zeta(-m) = \frac{B_{m+1}}{m+1} and that odd Bernoulli numbers vanish. if (-m % 2 == 0) { continue; } // This could theoretically overrun our stored Bernoulli values, but if we haven't converged after 32 negative terms, we are in trouble. //f += c * AdvancedIntegerMath.Bernoulli[(-m + 1) / 2] / (-m + 1); f += c * AdvancedMath.RiemannZeta(m); } else if (m == 1) { // Special term in place of \zeta(1). f += c * (AdvancedIntegerMath.HarmonicNumber(n - 1) - Math.Log(-lnx)); } else { // Otherwise just compute \zeta(m). // We could reduce even m to Bernoulli references but then we would be in trouble for n > 32. f += c * AdvancedMath.RiemannZeta(m); } if (f == f_old) { return(f); } } throw new NonconvergenceException(); }
/// <summary> /// Computes the logrithm of the factorial of an integer. /// </summary> /// <param name="n">The argument, which must be non-negative.</param> /// <returns>The value of ln(n!).</returns> /// <remarks> /// <para>This function provides accurate values of ln(n!) even for values of n which would cause n! to overflow.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is negative.</exception> /// <seealso cref="Factorial"/> public static double LogFactorial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException(nameof(n)); } else if (n < factorialTable.Length) { return(Math.Log((double)factorialTable[n])); } else { return(AdvancedMath.LogGamma(n + 1)); } }
/// <summary> /// Computes the factorial of an integer. /// </summary> /// <param name="n">The argument, which must be non-negative.</param> /// <returns>The value of n!.</returns> /// <remarks> /// <para>The factorial of an integer n is the product of all integers from 1 to n. For example, 4! = 4 * 3 * 2 * 1 = 24.</para> /// <para>n! also has a combinatorial intrepretation as the number of permutations of n objects. For example, a set of 3 /// objects (abc) has 3! = 6 permutations: (abc), (bac), (cba), (acb), (cab), (bca).</para> /// <para>Because n! grows extremely quickly with increasing n, we return the result as a double, even though /// the value is always an integer. (13! would overlow an int, 21! would overflow a long, 171! overflows even a double.)</para> /// <para>In order to deal with factorials of larger numbers, you can use the <see cref="LogFactorial"/> method, which /// returns accurate values of ln(n!) even for values of n for which n! would overflow a double.</para> /// <para>The factorial is generalized to non-integer arguments by the Γ function (<see cref="AdvancedMath.Gamma(double)"/>).</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is negative.</exception> /// <seealso cref="LogFactorial"/> /// <seealso cref="AdvancedMath.Gamma(double)"/> /// <seealso href="http://en.wikipedia.org/wiki/Factorial"/> public static double Factorial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException("n"); } else if (n < factorialTable.Length) { return((double)factorialTable[n]); } else { return(Math.Round(AdvancedMath.Gamma(n + 1))); } }
private static double LogDoubleFactorial_Gamma(int n) { if (n % 2 == 0) { // m = n/2, n!! = 2^m Gamma(m+1) int m = n / 2; return(m * Global.LogTwo + AdvancedMath.LogGamma(m + 1.0)); } else { // m = (n+1)/2, n!! = 2^m Gamma(m+1/2) / Sqrt(PI) int m = (n + 1) / 2; return(m * Global.LogTwo + AdvancedMath.LogGamma(m + 0.5) - Math.Log(Math.PI) / 2.0); } }
private static double JacobiSn_ViaRangeReduction(double u, double k, out long u0, out double u1) { Debug.Assert(u >= 0.0); double m = k * k; // First we compare u to a quick-to-compute lower bound for K / 2. // If it's below the bound, we can move directly to series for sn without having // to compute K or perform range reduction. double K = LowerBoundK(m); if (u <= K / 2.0) { u0 = 0; u1 = u; } else { // Too bad, we need to actually compute K and range reduce. K = AdvancedMath.EllipticK(k); double v = u / K; double v0 = Math.Round(v); if (v < Int64.MaxValue) { u0 = (long)v0; u1 = (v - v0) * K; } else { u0 = Int64.MaxValue; u1 = 0.0; } } Debug.Assert(Math.Abs(u1) <= K / 2.0); // Note that for u >> K, this has the same problem as naïve trig function calculations // for x >> 2 \pi: we loose a lot of accuracy for u1 because it is computed via subtraction. // There is not much we can do about this, though, short of moving to arbitrary-precision // arithmetic, because K is different for each value of m. // Compute sn of the reduced -K/2 < u1 < K/2 return(JacobiSn_ReduceToSeries(u1, m)); // We should be able to do even better: |u1| <= K / 4. Our first attempt had some problems, // and the series still work for the larger range, so leave it as is for now. }
/// <summary> /// Computes the complex Faddeeva function. /// </summary> /// <param name="z">The complex argument.</param> /// <returns>The complex value of w(z).</returns> /// <remarks> /// <para>The Faddeeva function w(z) is related to the error function with a complex argument.</para> /// <img src="../images/FaddeevaErfcRelation.png" /> /// <para>It also has an integral representation.</para> /// <img src="../images/FaddeevaIntegral.png" /> /// <para>For purely imaginary values, it reduces to the complementary error function (<see cref="AdvancedMath.Erfc"/>). /// For purely real values, it reduces to Dawson's integral (<see cref="AdvancedMath.Dawson"/>).</para> /// <para>It appears in the computation of the Voigt line profile function V(x;σ,γ).</para> /// <img src="../images/Voigt.png" /> /// <para>Near the origin, w(z) ≈ 1. To accurately determine w(z) - 1 in this region, use the <see cref="Erf"/> /// function. Away from the origin near the large negative imaginary axis, the magnitude w(z) increases rapidly and /// may overflow.</para> /// <para>The image below shows the complex Faddeeva function near the origin, using domain coloring.</para> /// <img src="../images/ComplexFaddeevaPlot.png" /> /// </remarks> /// <seealso cref="AdvancedComplexMath.Erf"/> /// <seealso cref="AdvancedMath.Erf" /> /// <seealso cref="AdvancedMath.Erfc" /> /// <seealso cref="AdvancedMath.Dawson"/> /// <seealso href="http://en.wikipedia.org/wiki/Voigt_profile" /> public static Complex Faddeeva(Complex z) { // use reflection formulae to ensure that we are in the first quadrant if (z.Im < 0.0) { return(2.0 * ComplexMath.Exp(-z * z) - Faddeeva(-z)); } if (z.Re < 0.0) { return(Faddeeva(-z.Conjugate).Conjugate); } double r = ComplexMath.Abs(z); if (r < 2.0) { // use series for small z return(ComplexMath.Exp(-z * z) * (1.0 - Erf_Series(-ComplexMath.I * z))); //return (Faddeeva_Series(z)); } else if ((z.Im < 0.1) && (z.Re < 30.0)) { // this is a special, awkward region // along the real axis, Re{w(x)} ~ e^{-x^2}; the Weideman algorthm doesen't compute this small number // well and the Laplace continued fraction misses it entirely; therefore very close to the real axis // we will use an analytic result on the real axis and Taylor expand to where we need to go. // unfortunately the Taylor expansion converges poorly for large x, so we drop this work-arround near x~30, // when this real part becomes too small to represent as a double anyway double x = z.Re; double y = z.Im; return(Faddeeva_Taylor(new Complex(x, 0.0), Math.Exp(-x * x) + 2.0 * AdvancedMath.Dawson(x) / Global.SqrtPI * ComplexMath.I, new Complex(0.0, y))); } else if (r > 7.0) { // use Laplace continued fraction for large z return(Faddeeva_ContinuedFraction(z)); } else { // use Weideman algorithm for intermediate region return(Faddeeva_Weideman(z)); } }
private static double ModifiedBesselI_Series(double nu, double x) { double dI = AdvancedMath.PowOverGammaPlusOne(x / 2.0, nu); double I = dI; double xx = x * x / 4.0; for (int k = 1; k < Global.SeriesMax; k++) { double I_old = I; dI = dI * xx / (nu + k) / k; I += dI; if (I == I_old) { return(I); } } throw new NonconvergenceException(); }
private static double ModifiedBesselI_Series(double nu, double x) { double x2 = 0.5 * x; double xx = x2 * x2; double dI = AdvancedMath.PowerOverFactorial(x2, nu); double I = dI; for (int k = 1; k < Global.SeriesMax; k++) { double I_old = I; dI = dI * xx / (k * (nu + k)); I += dI; if (I == I_old) { return(I); } } throw new NonconvergenceException(); }
// **** Real order Bessel functions **** // Series development of Bessel J // J_{\nu}(x) = \sum_{k=0}^{\infty} \frac{( )^{\nu + 2k}}{\Gamma(\nu + 2k + 1)} // As can be seen by comparing leading term and first correction, this is good for z <~ \max(1 , \sqrt{\nu}) // For nu=0, it requires 10 terms at x~1.0, 25 terms at x~10.0, but accuracy in the last few digits suffers that far out // Gets better for higher nu (for nu=10, only 20 terms are required at x~10.0 and all digits are good), but only with sqrt(nu) private static double BesselJ_Series(double nu, double x) { double z = 0.5 * x; double dJ = AdvancedMath.PowerOverFactorial(z, nu); double J = dJ; double zz = -z * z; for (int k = 1; k < Global.SeriesMax; k++) { double J_old = J; dJ *= zz / (k * (nu + k)); J += dJ; if (J == J_old) { return(J); } } throw new NonconvergenceException(); }
private static double ModifiedBesselI_Series(double nu, double x) { double x2 = x / 2.0; double dI = Math.Pow(x2, nu) / AdvancedMath.Gamma(nu + 1.0); double I = dI; double xx = x2 * x2; for (int k = 1; k < Global.SeriesMax; k++) { double I_old = I; dI = dI * xx / (nu + k) / k; I += dI; if (I == I_old) { return(I); } } throw new NonconvergenceException(); }
private static double BerSeries(double nu, double x) { double c = Math.Cos(3.0 * nu * Math.PI / 4.0); double s = Math.Sin(3.0 * nu * Math.PI / 4.0); double xh = x / 2.0; double xh2 = xh * xh; double df = Math.Pow(xh, nu) / AdvancedMath.Gamma(nu + 1.0); double f_old = 0.0; double f = c * df; for (int k = 1; k < Global.SeriesMax; k++) { // we look two values back because for some values of nu (e.g. 0.0) only alternating // powers contribute; for these values we would always have f_old == f for non-contributing // powers and the series would terminate early double f_old_old = f_old; f_old = f; df *= xh2 / k / (nu + k); switch (k % 4) { case 0: f += c * df; break; case 1: f -= s * df; break; case 2: f -= c * df; break; case 3: f += s * df; break; } if (f == f_old_old) { return(f); } } throw new NonconvergenceException(); }
/// <summary> /// Computes the exponential integral. /// </summary> /// <param name="n">The order parameter.</param> /// <param name="x">The argument, which must be non-negative.</param> /// <returns>The value of E<sub>n</sub>(x).</returns> /// <remarks> /// <para>The exponential integral is defined as:</para> /// <img src="../images/EIntegral.png" /> /// <para>It is related to the incomplete Gamma function for negative, integer shape parameters by Γ(-k, x) = Ei<sub>k+1</sub>(x) / x<sup>k</sup>.</para> /// <para>In hydrology, E<sub>1</sub>(x) is sometimes called the Well function.</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="x"/> is negative.</exception> public static double IntegralE(int n, double x) { if (x < 0.0) { throw new ArgumentOutOfRangeException("x"); } // special case x = 0 if (x == 0.0) { if (n <= 1) { return(Double.PositiveInfinity); } else { return(1.0 / (n - 1)); } } if (n < 0) { // negative n is expressible using incomplete Gamma return(AdvancedMath.Gamma(1 - n, x) / MoreMath.Pow(x, 1 - n)); } else if (n == 0) { // special case n=0 return(Math.Exp(-x) / x); } else if (x < 2.0) { // use series for x < 1 return(IntegralE_Series(n, x)); } else { // use continued fraction for x > 1 return(IntegralE_ContinuedFraction(n, x)); } }
// this is just an integer version of the series we implement below for doubles; // having an integer-specific version is slightly faster private static double BesselJ_Series(int n, double x) { Debug.Assert(n >= 0); Debug.Assert(x >= 0.0); double z = 0.5 * x; double dJ = AdvancedMath.PowerOverFactorial(z, n); double J = dJ; double zz = -z * z; for (int k = 1; k < Global.SeriesMax; k++) { double J_old = J; dJ *= zz / ((n + k) * k); J += dJ; if (J == J_old) { return(J); } } throw new NonconvergenceException(); }
private static double AiryBi_Series(double x) { double p = 1.0 / (Math.Pow(3.0, 1.0 / 6.0) * AdvancedMath.Gamma(2.0 / 3.0)); double q = x * (Math.Pow(3.0, 1.0 / 6.0) / AdvancedMath.Gamma(1.0 / 3.0)); double f = p + q; double x3 = x * x * x; for (int k = 0; k < Global.SeriesMax; k += 3) { double f_old = f; p *= x3 / ((k + 2) * (k + 3)); q *= x3 / ((k + 3) * (k + 4)); f += p + q; if (f == f_old) { return(f); } } throw new NonconvergenceException(); }
/// <summary> /// Compute the complex log Gamma function. /// </summary> /// <param name="z">The complex argument.</param> /// <returns>The principal complex value y for which exp(y) = Γ(z).</returns> /// <seealso cref="AdvancedMath.LogGamma" /> /// <seealso href="http://mathworld.wolfram.com/LogGammaFunction.html"/> public static Complex LogGamma(Complex z) { if (z.Im == 0.0 && z.Re < 0.0) { // Handle the pure negative case explicitly. double re = Math.Log(Math.PI / Math.Abs(MoreMath.SinPi(z.Re))) - AdvancedMath.LogGamma(1.0 - z.Re); double im = Math.PI * Math.Floor(z.Re); return(new Complex(re, im)); } else if (z.Re > 16.0 || Math.Abs(z.Im) > 16.0) { // According to https://dlmf.nist.gov/5.11, the Stirling asymptoic series is valid everywhere // except on the negative real axis. So at first I tried to use it for |z.Im| > 0, |z| > 16. But in practice, // it exhibits false convergence close to the negative real axis, e.g. z = -16 + i. So I have // moved to requiring |z| large and reasonably far from the negative real axis. return(Stirling.LogGamma(z)); } else if (z.Re >= 0.125) { return(Lanczos.LogGamma(z)); } else { // For the remaining z < 0, we need to use the reflection formula. // For large z.Im, SinPi(z) \propto e^{\pi |z.Im|} overflows even though its log does not. // It's possible to do some algebra to get around that problem, but it's not necessary // because for z.Im that big we would have used the Stirling series. Complex f = ComplexMath.Log(Math.PI / ComplexMath.SinPi(z)); Complex g = Lanczos.LogGamma(1.0 - z); // The reflection formula doesn't stay on the principal branch, so we need to add a multiple of 2 \pi i // to fix it up. See Hare, "Computing the Principal Branch of Log Gamma" for how to do this. // https://pdfs.semanticscholar.org/1c9d/8865836a312836500126cb47c3cbbed3043e.pdf Complex h = new Complex(0.0, 2.0 * Math.PI * Math.Floor(0.5 * (z.Re + 0.5))); if (z.Im < 0.0) { h = -h; } return(f - g + h); } }
/// <summary> /// Computes the given harmonic number. /// </summary> /// <param name="n">The index of the harmonic number to compute, which must be non-negative.</param> /// <returns>The harmonic number H<sub>n</sub>.</returns> /// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is negative.</exception> /// <remarks> /// <para>H<sub>n</sub> is the nth partial sum of the harmonic series.</para> /// <img src="..\images\HarmonicSeries.png" /> /// <para>Since the harmonic series diverges, H<sub>n</sub> grows without bound as n increases, but /// it does so extremely slowly, approximately as log(n).</para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is negative.</exception> /// <seealso href="http://en.wikipedia.org/wiki/Harmonic_series_(mathematics)"/> public static double HarmonicNumber(int n) { if (n < 0) { throw new ArgumentOutOfRangeException(nameof(n)); } else if (n < 32) { // for small values, just add up the harmonic series double H = 0.0; for (int i = 1; i <= n; i++) { H += 1.0 / i; } return(H); } else { // for large values, use the digamma function // this will route to the the Stirling asymptotic expansion return(AdvancedMath.Psi(n + 1) + AdvancedMath.EulerGamma); } }
/// <summary> /// Computes the Riemann zeta function. /// </summary> /// <param name="x">The argument.</param> /// <returns>The value ζ(s).</returns> /// <remarks> /// <para>The Riemann ζ function can be defined as the sum of the <paramref name="x"/>th inverse power of the natural numbers.</para> /// <img src="../images/ZetaSeries.png" /> /// </remarks> /// <seealso href="http://en.wikipedia.org/wiki/Riemann_zeta_function"/> /// <seealso href="https://mathworld.wolfram.com/RiemannZetaFunction.html"/> /// <seealso href="https://dlmf.nist.gov/25"/> public static double RiemannZeta(double x) { if (x < 0.0) { // for negative numbers, use the reflection formula double t = 1.0 - x; return(2.0 * Math.Pow(2.0 * Math.PI, -t) * MoreMath.CosPi(0.5 * t) * AdvancedMath.Gamma(t) * RiemannZeta(t)); } else { double xm1 = x - 1.0; if (Math.Abs(xm1) < 0.25) { // near the singularity, use the Stjielts expansion return(RiemannZeta_LaurentSeries(xm1)); } else { // call Dirichlet function, which converges faster return(DirichletEta(x) / (1.0 - Math.Pow(2.0, 1.0 - x))); } } }
/// <summary> /// Computes the given Bernoulli number. /// </summary> /// <param name="n">The index of the Bernoulli number to compute, which must be non-negative.</param> /// <returns>The Bernoulli number B<sub>n</sub>.</returns> /// <exception cref="ArgumentOutOfRangeException"><paramref name="n"/> is negative.</exception> /// <remarks> /// <para>B<sub>n</sub> vanishes for all odd n except n=1. For n about 260 or larger, B<sub>n</sub> overflows a double.</para> /// </remarks> /// <seealso href="http://en.wikipedia.org/wiki/Bernoulli_number"/> /// <seealso href="http://mathworld.wolfram.com/BernoulliNumber.html"/> public static double BernoulliNumber(int n) { if (n < 0) { throw new ArgumentOutOfRangeException(nameof(n)); } // B_1 is the only odd Bernoulli number. if (n == 1) { return(-1.0 / 2.0); } // For all other odd arguments, return zero. if (n % 2 != 0) { return(0.0); } // If the argument is small enough, look up the answer in our stored array. int m = n / 2; if (m < Bernoulli.Length) { return(Bernoulli[m]); } // Otherwise, use the relationship with the Riemann zeta function to get the Bernoulli number. // Since this is only done for large n, it would probably be faster to just sum the zeta series explicitly here. double B = 2.0 * AdvancedMath.RiemannZeta(n) / AdvancedMath.PowOverGammaPlusOne(Global.TwoPI, n); if (m % 2 == 0) { B = -B; } return(B); }
/// <summary> /// Computes the Gamma function. /// </summary> /// <param name="x">The argument.</param> /// <returns>The value of Γ(x).</returns> /// <remarks> /// <para>The Gamma function is a generalization of the factorial (see <see cref="AdvancedIntegerMath.Factorial"/>) to arbitrary real values.</para> /// <img src="../images/GammaIntegral.png" /> /// <para>For positive integer arguments, this integral evaluates to Γ(n+1)=n!, but it can also be evaluated for non-integer z.</para> /// <para>Because Γ(x) grows beyond the largest value that can be represented by a <see cref="System.Double" /> at quite /// moderate values of x, you may find it useful to work with the <see cref="LogGamma" /> method, which returns ln(Γ(x)).</para> /// <para>To evaluate the Gamma function for a complex argument, use <see cref="AdvancedComplexMath.Gamma" />.</para> /// <h2>Domain, Range, and Accuracy</h2> /// <para>The function is defined for all x. It has poles at all negative integers and at zero; the method returns <see cref="Double.NaN"/> for these arguments. For positive /// arguments, the value of the function increases rapidly with increasing argument. For values of x greater than about 170, the value of the function exceeds /// <see cref="Double.MaxValue"/>; for these arguments the method returns <see cref="Double.PositiveInfinity"/>. The method is accurate to full precision over its entire /// domain.</para> /// </remarks> /// <seealso cref="AdvancedIntegerMath.Factorial" /> /// <seealso cref="LogGamma" /> /// <seealso cref="AdvancedComplexMath.Gamma" /> /// <seealso href="http://en.wikipedia.org/wiki/Gamma_function" /> /// <seealso href="http://mathworld.wolfram.com/GammaFunction.html" /> /// <seealso href="http://dlmf.nist.gov/5">DLMF on the Gamma Function</seealso> public static double Gamma(double x) { if (x <= 0.0) { if (x == Math.Ceiling(x)) { // poles at zero and negative integers return(Double.NaN); } else { return(Math.PI / Gamma(-x) / (-x) / AdvancedMath.Sin(0.0, x / 2.0)); } } else if (x < 16.0) { return(Lanczos.Gamma(x)); } else { return(Stirling.Gamma(x)); //return (Math.Exp(LogGamma_Stirling(x))); } }
private static void Gamma_Temme(double a, double x, out double P, out double Q) { double u = Math.Log(x / a); // compute argument of error function, which is almost (x-a)/sqrt(a) double dz = 1.0; double z = dz; for (int i = 3; true; i++) { if (i > Global.SeriesMax) { throw new NonconvergenceException(); } double z_old = z; dz *= u / i; z += dz; if (z == z_old) { break; } } z = u * Math.Sqrt(a * z / 2.0); // the first approximation is just the almost-Gaussian one if (z > 0) { Q = AdvancedMath.Erfc(z) / 2.0; P = 1.0 - Q; } else { P = AdvancedMath.Erfc(-z) / 2.0; Q = 1.0 - P; } // compute Temme's correction to the Gaussian approximation double R0 = Math.Exp(-z * z) / Math.Sqrt(Global.TwoPI * a); double S0 = 0.0; double ai = 1.0; for (int i = 0; i < TemmeD.Length; i++) { double dS = 0.0; double uj = 1.0; for (int j = 0; j < TemmeD[i].Length; j++) { dS += TemmeD[i][j] * uj; uj *= u; } S0 += dS / ai; ai *= a; } double R = R0 * S0; Q = Q + R; P = P - R; }
/// <summary> /// Compute the Riemann zeta function. /// </summary> /// <param name="s">The argument.</param> /// <returns>The value ζ(s).</returns> /// <remarks> /// <para>The Riemann ζ function can be defined as the sum of the <paramref name="s"/>th inverse power of the natural numbers.</para> /// <img src="../images/ZetaSeries.png" /> /// </remarks> /// <seealso href="http://en.wikipedia.org/wiki/Riemann_zeta_function"/> public static double RiemannZeta(double s) { if (s < 0.0) { // for negative numbers, use the reflection formula double t = 1.0 - s; double z = 2.0 * Math.Pow(Global.TwoPI, -t) * Math.Cos(Global.HalfPI * t) * AdvancedMath.Gamma(t) * RiemannZeta(t); return(z); } else { if (Math.Abs(s - 1.0) < 0.25) { // near the sigularity, use the Stjielts expansion return(RiemannZeta_Series(s - 1.0)); } else { // call Dirichlet function, which converges faster return(DirichletEta(s) / (1.0 - Math.Pow(2.0, 1.0 - s))); } } }
// The point of this method is to compute // e G_{e}(x) = \frac{1}{\Gamma(x)} - \frac{1}{\Gamma(x + e)} // To do this, we will re-use machinery that we developed to accurately compute the Pochhammer symbol // (x)_e = \frac{\Gamma(x + e)}{\Gamma(x)} // To do this, we use the reduced log Pochhammer function L_{e}(x). // \ln((x)_e) = e L_{e}(x) // To see why we developed this function, see the Pochhamer code. The Lanczos apparatus allows us to compute // L_{e}(x) accurately, even in the small-e limit. To see how look at the Pochhammer code. // To connect G_{e}(x) to L_{e}(x), write // e G_{e}(x) = \frac{(x)_e - 1}{\Gamma(x + e)} // = \frac{\exp(\ln((x)_e)) - 1}{\Gamma(x + e)} // = \frac{\exp(e L_{e}(x)) - 1}{\Gamma(x + e)} // G_{e}(x) = \frac{E_{e}(L_{e}(x))}{\Gamma(x + e)} // where e E_{e}(x) = \exp(e x) - 1, which we also know how to compute accurately even in the small-e limit. // This deals with G_{e}(x) for positive x. But L_{e}(x) and \Gamma(x + e) still blow up for x or x + e // near a non-positive integer, and our Lanczos machinery for L_{e}(x) assumes positive x. To deal with // the left half-plane, use the reflection formula // \Gamma(z) \Gamma(1 - z) = \frac{\pi}{\sin(\pi z)} // on both Gamma functions in the definition of G_{e}(x) to get // e G_{e}(x) = \frac{\sin(\pi x)}{\pi} \Gamma(1 - x) - \frac{\sin(\pi x + \pi e)}{\pi} \Gamma(1 - x - e) // Use the angle addition formula on the second \sin and the definition of the Pochhammer symbol // to get all terms proportional to one Gamma function with a guaranteed positive argument. // \frac{e G_{e}(x)}{\Gamma(1 - x - e)} = // \frac{\sin(\pi x)}{\pi} \left[ (1 - x - e)_{e} - \cos(\pi e) \right] - \frac{\cos(\pi x) \sin(\pi e)}{\pi} // We need the RHS ~e to for small e. That's manifestly true for the second term because of the factor \sin(\pi e). // It's true for the second term because (1 - x - e)_{e} and \cos(\pi e) are both 1 + O(e), but to avoid cancelation // we need to make it manifest. Write // (y)_{e} = \exp(e L_{e}(y)) - 1 + 1 = e E_{e}(L_{e}(y)) + 1 // and // 1 - \cos(\pi e) = 2 \sin^2(\half \pi /e) // Now we can divide through by e. // \frac{G_{e}(x)}{\Gamma(y)} = // \sin(\pi x) \left[ \frac{E_{e}(L_{e}(y))}{\pi} + \frac{\sin^2(\half \pi e)}{\half \pi e} \right] - // \cos(\pi x) \frac{\sin(\pi e)}{\pi e} // and everything can be safely computed. // This is a different approach than the one in the Michel & Stoitsov paper. Their approach also used // the Lanczos evaluation of the Pochhammer symbol, but had some deficiencies. // For example, for e <~ 1.0E-15 and x near a negative integer, it gives totally wrong // answers, and the answers loose accuracy for even larger e. This is because the // computation relies on a ratio of h to Gamma, both of which blow up in this region. private static double NewG(double x, double e) { Debug.Assert(Math.Abs(e) <= 0.5); // It would be better to compute G outright from Lanczos, rather than via h. Can we do this? // Also, we should probably pick larger of 1 - x and 1 - x - e to use as argument of // Gamma function factor. double y = x + e; if ((x < 0.5) || (y < 0.5)) { double h = MoreMath.ReducedExpMinusOne(Lanczos.ReducedLogPochhammer(1.0 - y, e), e); if (e == 0.0) { double t = MoreMath.SinPi(x) * h / Math.PI - MoreMath.CosPi(x); return(AdvancedMath.Gamma(1.0 - y) * t); } else { double s = MoreMath.SinPi(e) / (Math.PI * e); double s2 = MoreMath.Sqr(MoreMath.SinPi(e / 2.0)) / (Math.PI * e / 2.0); double t = MoreMath.SinPi(x) * (h / Math.PI + s2) - MoreMath.CosPi(x) * s; return(AdvancedMath.Gamma(1.0 - y) * t); } } return(MoreMath.ReducedExpMinusOne(Lanczos.ReducedLogPochhammer(x, e), e) / AdvancedMath.Gamma(x + e)); }
// Our approach to evaluating the transformed series is taken from Michel & Stoitsov, "Fast computation of the // Gauss hypergeometric function with all its parameters complex with application to the Poschl-Teller-Ginocchio // potential wave functions" (https://arxiv.org/abs/0708.0116). Michel & Stoitsov had a great idea, but their // exposition leaves much to be desired, so I'll put in a lot of detail here. // The basic idea is an old one: use the linear transformation formulas (A&S 15.3.3-15.3.9) to map all x into // the region [0, 1/2]. The x -> (1-x) transformation, for example, looks like // F(a, b, c, x) = // \frac{\Gamma(c) \Gamma(c-a-b)}{\Gamma(c-a) \Gamma(c-b)} F(a, b, a+b-c+1, 1-x) + // \frac{\Gamma(c) \Gamma(a+b-c)}{\Gamma(a) \Gamma(b)} F(c-a, c-b, c-a-b, 1-x) (1-x)^{c-a-b} // When c-a-b is close to an integer, though, there is a problem. Write c = a + b + m + e, where m is a positive integer // and |e| <= 1/2. The transformed expression becomes: // \frac{F(a, b, c, x)}{\Gamma(c)} = // \frac{\Gamma(m + e)}{\Gamma(b + m + e) \Gamma(a + m + e)} F(a, b, 1 - m - e, 1 - x) + // \frac{\Gamma(-m - e)}{\Gamma(a) \Gamma(b)} F(b + m + e, a + m + e, 1 + m + e, 1 - x) (1-x)^{m + e} // In the first term, the F-function blows up as e->0 (or, if m=0, \Gamma(m+e) blows up), and in the second term // \Gamma(-m-e) blows up in that limit. By finding the divergent O(1/e) and the sub-leading O(1) terms, it's not too // hard to show that the divgences cancel, leaving a finite result, and to derive that finite result for e=0. // (A&S gives the result, and similiar ones for the divergent limits of other linear transformations.) // But we still have a problem for e small-but-not-zero. The pre-limit expressions will have large cancelations. // We can't ignore O(e) and higher terms, but developing a series in e in unworkable -- the higher derivatives // rapidly become complicated and unwieldy. No expressions in A&S get around this problem, but we will now // develop an approach that does. // Notice the divergence of F(a, b, 1 - m - e, 1 - x) is at the mth term, where (1 - m - e)_{m} ~ e. Pull out // the finite sum up to the (m-1)th term // \frac{F_0}{\Gamma(c)} = \frac{\Gamma(m+e)}{\Gamma(b + m + e) \Gamma(a + m + e)} // \sum_{k=0}^{m-1} \frac{(a)_k (b)_k}{(1 - m - e)_{k}} \frac{(1-x)^k}{k!} // The remainder, which contains the divergences, is: // \frac{F_1}{\Gamma(c)} = // \frac{\Gamma(m + e)}{\Gamma(b + m + e) \Gamma(a + m + e)} \sum_{k=0}^{\infty} \frac{(1-x)^{m + k}}{\Gamma(1 + m + k)} // \frac{\Gamma(a + m + k) \Gamma(b + m + k) \Gamma(1 - m - e}{\Gamma(a) \Gamma(b) \Gamma(1 - e + k)} + // \frac{\Gamma(-m - e)}{\Gamma(a) \Gamma(b)} \sum_{k=0}^{\infty} \frac{(1-x)^{m + e + k}}{\Gamma(1 + k)} // \frac{\Gamma(b + m + e + k) \Gamma(a + m + e + k) \Gamma(1 + m + e)}{\Gamma(b + m + e) \Gamma(a + m + e) \Gamma(1 + m + e + k)} // where we have shifted k by m in the first sum. Use the \Gamma reflection formulae // \Gamma(m + e) \Gamma(1 - m - e) = \frac{\pi}{\sin(\pi(m + e))} = \frac{(-1)^m \pi}{\sin(\pi e)} // \Gamma(-m - e) \Gamma(1 + m + e) = \frac{\pi}(\sin(-\pi(m + e))} = -\frac{(-1)^m \pi}{\sin(\pi e)} // to make this // \frac{F_1}{\Gamma(c)} = // \frac{(-1)^m \pi}{\sin(\pi e)} \sum_{k=0}^{\infty} \frac{(1-x)^{m + k}}{\Gamma(a) \Gamma(b) \Gamma(a + m + e) \Gamma(b + m + e)} // \left[ \frac{\Gamma(a + m + k) \Gamma(b + m + k)}{\Gamma(1 + k - e) \Gamma(1 + m + k)} - // \frac{\Gamma(a + m + k + e) \Gamma(b + m + k + e)}{\Gamma(1 + k) \Gamma(1 + m + k + e)} (1-x)^e \right] // Notice that \frac{\pi}{\sin(\pi e)} diverges like ~1/e. And that the two terms in parenthesis contain exactly the // same products of \Gamma functions, execpt for having their arguments shifted by e. Therefore in the e->0 // limit their leading terms must cancel, leaving terms ~e, which will cancel the ~1/e divergence, leaving a finite result. // We would like to acomplish this cancelation analytically. This isn't too hard to do for e=0. Just write out a Taylor // series for \Gamma(z + e), keeping only terms up to O(e). The O(1) terms cancel, the e in front of the O(e) terms gets // absorbed into a finite prefactor \frac{\pi e}{\sin(\pi e)}, and we have a finite result. A&S gives the resulting expression. // The trouble is for e small-but-not-zero. If we try to evaluate the terms directly, we get cancelations between large terms, // leading the catastrophic loss of precision. If we try to use Taylor expansion, we need all the higher derivatives, // not just the first one, and the expressions rapidly become so complex and unwieldy as to be unworkable. // A good solution, introduced by Forrey, and refined by Michel & Stoistov, is to use finite differences instead // of derivatives. If we can express the difference betwen \Gamma(z) and \Gamma(z + e) as a function of z and e that // we can compute, then we can analytically cancel the divergent parts and be left with a finite expression involving // our finite difference function instead of an infinite series of Taylor series terms. For e=0, the finite difference // is just the first derivative, but for non-zero e, it implicitly sums the contributions of all Taylor series terms. // The finite difference function to use is: // e G_{e}(z) = \frac{1}{\Gamma(z)} - \frac{1}{\Gamma(z+e)} // I played around with a few others, e.g. the perhaps more obvious choice \frac{\Gamma(z+e)}{\Gamma(z)} = 1 + e P_{e}(z), // but the key advantage of G_{e}(z) is that it is perfectly finite even for non-positive-integer values of z and z+e, // because it uses the recriprocol \Gamma function. (I actually had a mostly-working algorithm using P_{e}(z), but it // broke down at non-positive-integer z, because P_{e}(z) itself still diverged for those values.) // For a discussion of how to actually compute G_{e}(z), refer to the method notes. // The next trick Michel & Stoistov use is to first concentrate just on the k=0 term. The relevent factor is // t = \frac{1}{\Gamma(a) \Gamma(b) \Gamma(a + m + e) \Gamma(b + m + e)} // \left[ \frac{\Gamma(a + m) \Gamma(b + m)}{\Gamma(1 - e) \Gamma(1 + m)} - // \frac{\Gamma(a + m + e) \Gamma(b + m + e)}{\Gamma(1) \Gamma(1 + m + e)} (1-x)^e \right] // = \frac{\Gamma(a + m) \Gamma(b + m)}{\Gamma(a) \Gamma(b)} // \left[ \frac{1}{\Gamma(a + m + e) \Gamma(b + m + e) \Gamma(1 + m) \Gamma(1 - e)} - // \frac{1}{\Gamma(a + m) \Gamma(b + m) \Gamma(1 + m + e) \Gamma(1)} (1-x)^e \right] // In the second step, we have put all the \Gamma functions we will need to compute for e-shifted arguments // in the denominator, which makes it easier to apply our definition of G_e(z), since there they are also // in the deonominator. // Before we begin using G_e(z), let's isolate the e-dependence of the (1-x)^e factor. Write // (1-x)^e = \exp(e \ln(1-x) ) = 1 + [ \exp(e \ln(1-x)) - 1 ] = 1 + e E_e(\ln(1-x)) // where e E_e(z) = \exp(e \ln(1-x)) - 1. We could continue like this, using G_(e) to // eliminate every \Gamma(z + e) in favor of G_e(z) and \Gamma(z), but by doing so we // would end up with terms containing two and more explicit powers of e, and products // of different G_e(z). That would be perfectly correct, but we end up with a nicer // expression if we instead "peel off" only one e-shifted function at a time, like this... // \frac{1}{\Gamma(a + m + e) \Gamma(b + m + e) \Gamma(1 + m) \Gamma(1 - e)} = // \frac{1}{\Gamma(a + m + e) \Gamma(b + m + e) \Gamma(1 + m) \Gamma(1)} + // \frac{e G_{-e}(1)}{\Gamma(a + m + e) \Gamma(b + m + e) \Gamma(1 + m)} // \frac{1}{\Gamma(a + m) \Gamma(b + m) \Gamma(1 + m + e)} = // \frac{1}{\Gamma(a + m + e) \Gamma(b + m) \Gamma(1 + m + e)} + // \frac{e G_e(a + m)}{\Gamma(b + m) \Gamma(1 + m + e)} // \frac{1}{\Gamma(a + m + e) \Gamma(b + m) \Gamma(1 + m + e)} = // \frac{1}{\Gamma(a + m + e) \Gamma(b + m + e) \Gamma(1 + m + e)} + // \frac{e G_e(b + m)}{Gamma(a + m + e) \Gamma(1 + m + e)} // \frac{1}{\Gamma(a + m + e) \Gamma(b + m + e) \Gamma(1 + m + e)} = // \frac{1}{\Gamma(a + m + e) \Gamma(b + m + e) \Gamma(1 + m)} - // \frac{e G_e(1 + m)}{\Gamma(a + m + e) \Gamma(b + m + e)} // Putting this all together, we have // t = \frac{1}{\Gamma(a + m + e) \Gamma(b + m + e)} \left[ \frac{e G_{-e}(1)}{\Gamma(1 + m) + e G_e(1 + m) \right] // - \frac{1}{\Gamma(1 + m + e)} \left[ \frac{e G_e(a + m)}{\Gamma(b + m)} + \frac{e G_e(b+m)}{\Gamma(a + m + e)} \right] // - \frac{1}{\Gamma(a + m + e) \Gamma(b + m + e) \Gamma(1 + m + e)} e E_e(\ln(1-x)) // which is, as promised, proportional to e. For some a, b, m, and e, some of these \Gamma functions will blow up, but they are // all in the denoninator, so that will just zero some terms. The G_e(z) that appear in the numerator are finite for all z. // So now we have the k=0 term. What about higer k terms? We could repeat this analysis, carrying along the k's, // and get an expression involving G_e(z) and E_e(z) for each k. Michel & Stoistov's last trick is to realize // we don't have to do this, but can instead use our original expressions for each term as a ratio of \Gamma // functions to derive a recurrence. Let u_k be the first term, v_k be the second term, so t_k = u_k + v_k. // Let r_k = u_{k+1} / u_{k} and s_k = v_{k+1} / v_{k}. It's easy to write down r_k and s_k because they // follow immediately from the \Gamma function recurrence. // r_{k} = \frac{(a + m + k)(b + m + k)}{(1 + k - e)(1 + m + k)} // s_{k} = \frac{(a + m + k + e)(b + m + k + e)}{(1 + k)(1 + m + k + e)} // Notice that r_k and s_k are almost equal, but not quite: they differ by O(e). To advance t_k, use // t_{k+1} = u_{k+1} + v_{k+1} = r_k u_k + s_k v_k = s_k (u_k + v_k) + (r_k - s_k) u_k // = s_k t_k + d_k * u_k // where d_k = r_k - s_k, which will be O(e), since r_k and s_k only differ by e-shifted arguments. // In the x -> (1-x), x -> 1/x, x -> x / (1-x), and x -> 1 - 1/x linear transformations, canceling divergences // appear when some arguments of the transformed functions are non-positive-integers. private static double Hypergeometric2F1_Series_OneOverOneMinusX(double a, int m, double e, double c, double x1) { Debug.Assert(m >= 0); Debug.Assert(Math.Abs(e) <= 0.5); Debug.Assert(Math.Abs(x1) <= 0.75); double b = a + m + e; double g_c = AdvancedMath.Gamma(c); //double rg_a = 1.0 / AdvancedMath.Gamma(a); double rg_b = 1.0 / AdvancedMath.Gamma(b); double rg_cma = 1.0 / AdvancedMath.Gamma(c - a); //double rg_cmb = 1.0 / AdvancedMath.Gamma(c - b); // Pochhammer product, keeps track of (a)_k (c-b)_k (x')^{a + k} double p = Math.Pow(x1, a); double f0 = 0.0; if (m > 0) { f0 = p; double q = 1.0; for (int k = 1; k < m; k++) { int km1 = k - 1; p *= (a + km1) * (c - b + km1) * x1; q *= (k - m - e) * k; f0 += p / q; } f0 *= g_c * rg_b * rg_cma * AdvancedMath.Gamma(m + e); p *= (a + (m - 1)) * (c - b + (m - 1)) * x1; } // Now compute the remaining terms with analytically canceled divergent parts. double t = rg_b * rg_cma * (NewG(1.0, -e) / AdvancedIntegerMath.Factorial(m) + NewG(m + 1, e)) - 1.0 / AdvancedMath.Gamma(1 + m + e) * (NewG(a + m, e) / AdvancedMath.Gamma(c - a - e) + NewG(c - a, -e) / AdvancedMath.Gamma(b)) - MoreMath.ReducedExpMinusOne(Math.Log(x1), e) / AdvancedMath.Gamma(a + m) / AdvancedMath.Gamma(c - a - e) / AdvancedMath.Gamma(m + 1 + e); t *= p; double f1 = t; double u = p * rg_b * rg_cma / AdvancedMath.Gamma(1.0 - e) / AdvancedIntegerMath.Factorial(m); for (int k = 0; k < Global.SeriesMax; k++) { double f1_old = f1; int k1 = k + 1; int mk1 = m + k1; double amk = a + m + k; double amke = amk + e; double cak = c - a + k; double cake = cak - e; double k1e = k1 - e; double mk1e = mk1 + e; double r = amk * cake / k1e / mk1; double s = amke * cak / mk1e / k1; // Compute (r - s) / e analytically because leading terms cancel double d = (amk * cake / mk1 - amk - cake - e + amke * cak / k1) / mk1e / k1e; t = (s * t + d * u) * x1; f1 += t; if (f1 == f1_old) { f1 *= ReciprocalSincPi(e) * g_c; if (m % 2 != 0) { f1 = -f1; } return(f0 + f1); } u *= r * x1; } throw new NonconvergenceException(); }
private static double Hypergeometric2F1_Series_OneMinusX(double a, double b, int m, double e, double x1) { Debug.Assert(m >= 0); Debug.Assert(Math.Abs(e) <= 0.5); Debug.Assert(Math.Abs(x1) <= 0.75); double c = a + b + m + e; // Compute all the gammas we will use. double g_c = AdvancedMath.Gamma(c); double rg_am = 1.0 / AdvancedMath.Gamma(a + m); double rg_bm = 1.0 / AdvancedMath.Gamma(b + m); double rg_ame = 1.0 / AdvancedMath.Gamma(a + m + e); double rg_bme = 1.0 / AdvancedMath.Gamma(b + m + e); double rg_m1e = 1.0 / AdvancedMath.Gamma(m + 1 + e); // Pochhammer product, keeps track of (a)_m (b)_m (x')^m double p = 1.0; // First compute the finite sum, which contains no divergent terms even for e = 0. double f0 = 0.0; if (m > 0) { double t0 = 1.0; f0 = t0; for (int k = 1; k < m; k++) { int km1 = k - 1; p *= (a + km1) * (b + km1) * x1; t0 *= 1.0 / (1.0 - m - e + km1) / k; f0 += t0 * p; } f0 *= g_c * rg_bme * rg_ame * AdvancedMath.Gamma(m + e); p *= (a + (m - 1)) * (b + (m - 1)) * x1; } // Now compute the remaining terms with analytically canceled divergent parts. double t = rg_bme * rg_ame * (NewG(1.0, -e) / AdvancedIntegerMath.Factorial(m) + NewG(m + 1, e)) - rg_m1e * (NewG(a + m, e) * rg_bme + NewG(b + m, e) * rg_am) - MoreMath.ReducedExpMinusOne(Math.Log(x1), e) * rg_am * rg_bm * rg_m1e; t *= p; double f1 = t; double u = p * rg_bme * rg_ame / AdvancedMath.Gamma(1.0 - e) / AdvancedIntegerMath.Factorial(m); for (int k = 0; k < Global.SeriesMax; k++) { double f1_old = f1; // Compute a bunch of sums we will use. int k1 = k + 1; int mk = m + k; int mk1 = mk + 1; double k1e = k1 - e; double amk = a + mk; double bmk = b + mk; double amke = amk + e; double bmke = bmk + e; double mk1e = mk1 + e; // Compute the ratios of each term. These are close, but not equal for e != 0. double r = amk * bmk / mk1 / k1e; double s = amke * bmke / mk1e / k1; // Compute (r - s) / e, with O(1) terms of (r - s) analytically canceled. double d = (amk * bmk / mk1 - (amk + bmk + e) + amke * bmke / k1) / mk1e / k1e; // Advance to the next term, including the correction for s != t. t = (s * t + d * u) * x1; f1 += t; if (f1 == f1_old) { f1 *= ReciprocalSincPi(e) * g_c; if (m % 2 != 0) { f1 = -f1; } return(f0 + f1); } // Advance the u term, which we will need for the next iteration. u *= r * x1; } throw new NonconvergenceException(); }