Example #1
0
 /// <summary>
 /// Computes the digamma function.
 /// </summary>
 /// <param name="x">The argument.</param>
 /// <returns>The value of &#x3C8;(x).</returns>
 /// <remarks>
 /// <para>The psi function, also called the digamma function, is the logrithmic derivative of the &#x393; function.</para>
 /// <img src="../images/DiGamma.png" />
 /// <para>To evaluate the Psi function for complex arguments, use <see cref="AdvancedComplexMath.Psi" />.</para>
 /// </remarks>
 /// <seealso cref="Gamma(double)"/>
 /// <seealso cref="AdvancedComplexMath.Psi"/>
 /// <seealso href="http://en.wikipedia.org/wiki/Digamma_function" />
 /// <seealso href="http://mathworld.wolfram.com/DigammaFunction.html" />
 public static double Psi(double x)
 {
     if (x <= 0.0)
     {
         if (x == Math.Ceiling(x))
         {
             // there are poles at zero and negative integers
             return(Double.NaN);
         }
         else
         {
             // use the reflection formula to change to a positive x
             return(Psi(1.0 - x) - Math.PI / Math.Tan(Math.PI * x));
         }
     }
     else if (x < 16.0)
     {
         return(Lanczos.Psi(x));
     }
     else
     {
         // for large arguments, the Stirling asymptotic expansion is faster than the Lanzcos approximation
         return(Stirling.Psi(x));
         //return (Psi_Stirling(x));
     }
 }
Example #2
0
 /// <summary>
 /// Computes the logarithm of the Beta function.
 /// </summary>
 /// <param name="a">The first parameter, which must be positive.</param>
 /// <param name="b">The second parameter, which must be positive.</param>
 /// <returns>The value of ln(B(a,b)).</returns>
 /// <remarks>
 /// <para>This function accurately computes ln(B(a,b)) even for values of a and b for which B(a,b) is
 /// too small or large to be represented by a double.</para>
 /// </remarks>
 /// <exception cref="ArgumentOutOfRangeException"><paramref name="a"/> or <paramref name="b"/> is negative or zero.</exception>
 /// <seealso cref="Beta(System.Double,System.Double)"/>
 public static double LogBeta(double a, double b)
 {
     if ((a > 16.0) && (b > 16.0))
     {
         return(Stirling.LogBeta(a, b));
     }
     else if (a < 0.0)
     {
         throw new ArgumentOutOfRangeException(nameof(a));
     }
     else if (b < 0.0)
     {
         throw new ArgumentOutOfRangeException(nameof(b));
     }
     else if (Double.IsNaN(a) || Double.IsNaN(b))
     {
         return(Double.NaN);
     }
     else if (a == 0.0 || b == 0.0)
     {
         return(Double.PositiveInfinity);
     }
     else
     {
         return(Lanczos.LogBeta(a, b));
     }
 }
Example #3
0
 /// <summary>
 /// Computes the Gamma function.
 /// </summary>
 /// <param name="x">The argument.</param>
 /// <returns>The value of &#x393;(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 &#x393;(n+1)=n!, but it can also be evaluated for non-integer z.</para>
 /// <para>Like the factorial, &#x393;(x) grows rapidly with increasing x; &#x393;(x) overflows <see cref="System.Double" />
 /// for all x larger than ~171. For arguments in this range, you may find it useful to work with the <see cref="LogGamma" /> method, which
 /// returns accurate values for ln(&#x393;(x)) even in the range for which &#x393;(x) overflows.</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.5)
     {
         // Use \Gamma(x) \Gamma(1-x) = \frac{\pi}{\sin(\pi x)} to move values close to and left of origin to x > 0
         return(Math.PI / MoreMath.SinPi(x) / Gamma(1.0 - x));
     }
     else if (x < 1.5)
     {
         return(GammaSeries.GammaOnePlus(x - 1.0));
     }
     else if (x < 2.5)
     {
         return(GammaSeries.GammaTwoPlus(x - 2.0));
     }
     else if (x < 16.0)
     {
         return(Lanczos.Gamma(x));
     }
     else if (x < 172.0)
     {
         return(Stirling.Gamma(x));
     }
     else if (x <= Double.PositiveInfinity)
     {
         // For x >~ 172, Gamma(x) overflows.
         return(Double.PositiveInfinity);
     }
     else
     {
         return(Double.NaN);
     }
 }
Example #4
0
 /// <summary>
 /// Computes the digamma function.
 /// </summary>
 /// <param name="x">The argument.</param>
 /// <returns>The value of &#x3C8;(x).</returns>
 /// <remarks>
 /// <para>The psi function, also called the digamma function, is the logrithmic derivative of the &#x393; function.</para>
 /// <img src="../images/DiGamma.png" />
 /// <para>Because it is defined as a <i>logarithmic</i> derivative, the digamma function does not overflow <see cref="System.Double"/>
 /// even for arguments for which <see cref="Gamma(double)"/> does.</para>
 /// <para>To evaluate the psi function for complex arguments, use <see cref="AdvancedComplexMath.Psi" />.</para>
 /// </remarks>
 /// <seealso cref="Gamma(double)"/>
 /// <seealso cref="AdvancedComplexMath.Psi"/>
 /// <seealso href="http://en.wikipedia.org/wiki/Digamma_function" />
 /// <seealso href="http://mathworld.wolfram.com/DigammaFunction.html" />
 public static double Psi(double x)
 {
     if (x < 0.5)
     {
         return(Psi(1.0 - x) - Math.PI / MoreMath.TanPi(x));
     }
     else if (x < 1.5)
     {
         return(GammaSeries.PsiOnePlus(x - 1.0));
     }
     else if (x < 2.5)
     {
         return(GammaSeries.PsiTwoPlus(x - 2.0));
     }
     else if (x < 16.0)
     {
         return(Lanczos.Psi(x));
     }
     else if (x <= Double.PositiveInfinity)
     {
         // For large arguments, the Stirling asymptotic expansion is faster than the Lanzcos approximation
         return(Stirling.Psi(x));
     }
     else
     {
         return(Double.NaN);
     }
 }
Example #5
0
        // one-argument functions

        /// <summary>
        /// Computes the natural logarithm of the Gamma function.
        /// </summary>
        /// <param name="x">The argument, which must be positive.</param>
        /// <returns>The log Gamma function ln(&#x393;(x)).</returns>
        /// <remarks>
        /// <para>Because &#x393;(x) grows rapidly for increasing positive x, it is often necessary to
        /// work with its logarithm in order to avoid overflow. This function returns accurate
        /// values of ln(&#x393;(x)) even for values of x which would cause &#x393;(x) to overflow.</para>
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="x"/> is negative.</exception>
        /// <seealso cref="Gamma(double)" />
        public static double LogGamma(double x)
        {
            if (x < 0.0)
            {
                throw new ArgumentOutOfRangeException(nameof(x));
            }
            else if (x < 16.0)
            {
                // For small arguments, use the Lanczos approximation.
                return(Lanczos.LogGamma(x));
            }
            else if (x < Double.PositiveInfinity)
            {
                // For large arguments, the asymptotic series is even faster than the Lanczos approximation.
                return(Stirling.LogGamma(x));
            }
            else if (x == Double.PositiveInfinity)
            {
                // Precisely at infinity x * Math.Log(x) - x => NaN, so special-case it.
                return(Double.PositiveInfinity);
            }
            else
            {
                return(Double.NaN);
            }
        }
Example #6
0
 private static double PowOverBeta(double a, double b, double x)
 {
     if ((a > 16.0) && (b > 16.0))
     {
         return(Stirling.PowOverBeta(a, b, x));
     }
     else
     {
         return(Math.Pow(x, a) * Math.Pow(1.0 - x, b) / Beta(a, b));
     }
 }
Example #7
0
        // 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));
            }
        }
Example #8
0
 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));
     }
 }
Example #9
0
        // This function computes x^n / n! or x^{\nu} / \Gamma(\nu + 1), which can easily become
        // Infinity/Infinity=NaN for large n if computed naively.

        internal static double PowerOverFactorial(double x, int n)
        {
            if (n <= 16)
            {
                // For maximum range, we should evaluate this using Lanczos, but
                // since we know we don't call it for x large enough for x^n to overflow,
                // this is safer and faster.
                return(MoreMath.Pow(x, n) / AdvancedIntegerMath.Factorial(n));
            }
            else
            {
                return(Stirling.PowerFactor(x, n));
            }
        }
Example #10
0
        // one-argument functions

        /// <summary>
        /// Computes the natural logrithm of the Gamma function.
        /// </summary>
        /// <param name="x">The argument, which must be positive.</param>
        /// <returns>The log Gamma function ln(&#x393;(x)).</returns>
        /// <remarks>
        /// <para>Because &#x393;(x) grows rapidly for increasing positive x, it is often necessary to
        /// work with its logarithm in order to avoid overflow. This function returns accurate
        /// values of ln(&#x393;(x)) even for values of x which would cause &#x393;(x) to overflow.</para>
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="x"/> is negative or zero.</exception>
        /// <seealso cref="Gamma(double)" />
        public static double LogGamma(double x)
        {
            if (x <= 0.0)
            {
                throw new ArgumentOutOfRangeException("x");
            }
            else if (x < 16.0)
            {
                // For small arguments, use the Lanczos approximation.
                return(Lanczos.LogGamma(x));
            }
            else
            {
                // For large arguments, the asymptotic series is even faster than the Lanczos approximation.
                return(Stirling.LogGamma(x));
                //return (LogGamma_Stirling(x));
            }
        }
Example #11
0
 /// <summary>
 /// Computes the lograrithm of the Beta function.
 /// </summary>
 /// <param name="a">The first parameter, which must be positive.</param>
 /// <param name="b">The second parameter, which must be positive.</param>
 /// <returns>The value of ln(B(a,b)).</returns>
 /// <remarks>
 /// <para>This function accurately computes ln(B(a,b)) even for values of a and b for which B(a,b) is
 /// too small or large to be represented by a double.</para>
 /// </remarks>
 /// <exception cref="ArgumentOutOfRangeException"><paramref name="a"/> or <paramref name="b"/> is non-positive.</exception>
 /// <seealso cref="Beta(System.Double,System.Double)"/>
 public static double LogBeta(double a, double b)
 {
     if (a <= 0.0)
     {
         throw new ArgumentOutOfRangeException("a");
     }
     if (b <= 0.0)
     {
         throw new ArgumentOutOfRangeException("b");
     }
     if ((a > 16.0) && (b > 16.0))
     {
         return(Stirling.LogBeta(a, b));
     }
     else
     {
         return(Lanczos.LogBeta(a, b));
     }
 }
Example #12
0
        // one-argument functions

        /// <summary>
        /// Computes the natural logarithm of the Gamma function.
        /// </summary>
        /// <param name="x">The argument, which must be positive.</param>
        /// <returns>The log Gamma function ln(&#x393;(x)).</returns>
        /// <remarks>
        /// <para>Because &#x393;(x) grows rapidly for increasing positive x, it is often necessary to
        /// work with its logarithm in order to avoid overflow. This function returns accurate
        /// values of ln(&#x393;(x)) even for values of x which would cause &#x393;(x) to overflow.</para>
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="x"/> is negative.</exception>
        /// <seealso cref="Gamma(double)" />
        public static double LogGamma(double x)
        {
            if (x < 0.0)
            {
                throw new ArgumentOutOfRangeException(nameof(x));
            }
            else if (x < 0.5)
            {
                // For small arguments, use the Lanczos approximation.
                return(Lanczos.LogGamma(x));
            }
            else if (x < 1.5)
            {
                // Use the series expansion near 1.
                return(GammaSeries.LogGammaOnePlus(x - 1.0));
            }
            else if (x < 2.5)
            {
                // The series expansion can be adapted near 2, too.
                return(GammaSeries.LogGammaTwoPlus(x - 2.0));
            }
            else if (x < 16.0)
            {
                // In between, we still use Lanczos.
                return(Lanczos.LogGamma(x));
            }
            else if (x < Double.PositiveInfinity)
            {
                // For large arguments, the asymptotic series is even faster than the Lanczos approximation.
                return(Stirling.LogGamma(x));
            }
            else if (x == Double.PositiveInfinity)
            {
                // Precisely at infinity x * Math.Log(x) - x => NaN, so special-case it.
                return(Double.PositiveInfinity);
            }
            else
            {
                return(Double.NaN);
            }
        }
Example #13
0
 /// <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) = &#x393;(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);
     }
 }
Example #14
0
 /// <summary>
 /// Computes the Gamma function.
 /// </summary>
 /// <param name="x">The argument.</param>
 /// <returns>The value of &#x393;(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 &#x393;(n+1)=n!, but it can also be evaluated for non-integer z.</para>
 /// <para>Because &#x393;(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(&#x393;(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)));
     }
 }
Example #15
0
        /// <summary>
        /// Computes the Pochammer symbol (x)<sub>y</sub>.
        /// </summary>
        /// <param name="x">The first argument.</param>
        /// <param name="y">The second argument.</param>
        /// <returns>The value of (x)<sub>y</sub>.</returns>
        /// <remarks>
        /// <para>The Pochhammer symbol is defined as a ratio of Gamma functions.</para>
        /// <para>For positive integer y, this is equal to a rising factorial product.</para>
        /// <para>Note that while the Pochhammer symbol notation is very common, combinatorialists sometimes
        /// use the same notation for a falling factorial.</para>
        /// <para>If you need to compute a ratio of Gamma functions, in most cases you should compute it by calling this
        /// function instead of taking the quotient of two calls to the <see cref="Gamma(double)"/> function,
        /// because there is a large fraction of the parameter space where Gamma(x+y) and Gamma(x) overflow,
        /// but their quotient does not, and is correctly computed by this method.</para>
        /// <para>This function accepts both positive and negative values for both x and y.</para>
        /// </remarks>
        /// <seealso href="http://mathworld.wolfram.com/PochhammerSymbol.html"/>
        public static double Pochhammer(double x, double y)
        {
            // Handle simplest cases quickly
            if (y == 0.0)
            {
                return(1.0);
            }
            else if (y == 1.0)
            {
                return(x);
            }

            // Should we also handle small integer y explicitly?

            // The key case of x and y positive is pretty simple. Both the Lanczos and Stirling forms of \Gamma
            // admit forms that preserve accuracy in \Gamma(x+y) / \Gamma(x) - 1 as y -> 0. It turns
            // out, however, to be ridiculously complicated to handle all the corner cases that appear in
            // other regions of the x-y plane.

            double z = x + y;

            if (x < 0.25)
            {
                bool xNonPositiveInteger = (Math.Round(x) == x);
                bool zNonPositiveInteger = (z <= 0.0) && (Math.Round(z) == z);

                if (xNonPositiveInteger)
                {
                    if (zNonPositiveInteger)
                    {
                        long   m;
                        double p;
                        if (z >= x)
                        {
                            m = (long)(z - x);
                            p = Pochhammer(-z + 1, m);
                        }
                        else
                        {
                            m = (long)(x - z);
                            p = 1.0 / Pochhammer(-x + 1, m);
                        }
                        if (m % 2 != 0)
                        {
                            p = -p;
                        }
                        return(p);
                    }
                    else
                    {
                        return(0.0);
                    }
                }

                if (y < 0.0)
                {
                    // x negative, y negative; we can transform to make x and y positive
                    return(1.0 / Pochhammer(1.0 - x, -y) * MoreMath.SinPi(x) / MoreMath.SinPi(z));
                }
                else if (z <= 0.25)
                {
                    // x negative, y positive, but not positive enough, so z still negative
                    // we can transform to make x and y positive
                    return(Pochhammer(1.0 - z, y) * MoreMath.SinPi(x) / MoreMath.SinPi(z));
                }
                else
                {
                    // x negative, y positive enough to make z positive
                    return(Gamma(1.0 - x) * Gamma(z) * MoreMath.SinPi(x) / Math.PI);
                }
            }
            else
            {
                if (z >= 0.25)
                {
                    // This is the standard case: x positive, y may be negative, but not negative enough to make z negative
                    double P;
                    if ((x > 16.0) && (z > 16.0))
                    {
                        P = Stirling.ReducedLogPochhammer(x, y);
                    }
                    else
                    {
                        P = Lanczos.ReducedLogPochhammer(x, y);
                    }
                    return(Math.Exp(y * P));
                }
                else
                {
                    // if y is very negative, z will also be negative, so we can transform one gamma
                    if (Math.Round(z) == z)
                    {
                        return(Double.PositiveInfinity);
                    }
                    else
                    {
                        return(Math.PI / (Gamma(x) * Gamma(1.0 - z) * MoreMath.SinPi(z)));
                    }
                }
            }
        }