// 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));
        }
Пример #2
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)));
                    }
                }
            }
        }