// // Series form for BesselY as z -> 0, // see: http://functions.wolfram.com/Bessel-TypeFunctions/BesselY/06/01/04/01/01/0003/ // This series is only useful when the second term is small compared to the first // otherwise we get catestrophic cancellation errors. // // Approximating tgamma(v) by v^v, and assuming |tgamma(-z)| < eps we end up requiring: // eps/2 * v^v(x/2)^-v > (x/2)^v or Math.Log(eps/2) > v Math.Log((x/2)^2/v) // /// <summary> /// Series form for Y{v}(x) as z -> 0 and v is not an integer /// <para>Y{v}(z) = (-((z/2)^v Cos(πv) Γ(-v))/π)) * 0F1(; 1+v; -z^2/4) - ((z/2)^-v Γ(v))/π) * 0F1(; 1-v; -z^2/4)</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> /// <seealso href="http://functions.wolfram.com/Bessel-TypeFunctions/BesselY/26/01/02/0001/"/> public static DoubleX Y_SmallArgSeries(double v, double x) { Debug.Assert(v >= 0 && x >= 0); Debug.Assert( !Math2.IsInteger(v), "v cannot be an integer"); DoubleX powDivGamma; double lnp = v * Math.Log(x / 2); if (v > DoubleLimits.MaxGamma || lnp < DoubleLimits.MinLogValue || lnp > DoubleLimits.MaxLogValue) { powDivGamma = DoubleX.Exp(lnp - Math2.Lgamma(v)); } else { DoubleX p = Math.Pow(x / 2, v); DoubleX gam = Math2.Tgamma(v); powDivGamma = p / gam; } // Series 1: -((z/2)^-v Γ(v))/π) * 0F1(; 1-v; -z^2/4) double s1 = HypergeometricSeries.Sum0F1(1 - v, -x * x / 4); DoubleX result1 = -(s1/Math.PI)/ powDivGamma; // Series2: -((z/2)^v Cos(πv) Γ(-v))/π)) * 0F1(; 1+v; -z^2/4) // using the reflection formula: Γ(-v) = -π/(Γ(v) * v * Sin(π * v)) // prefix = (z/2)^v * Cos(π * v)/(Γ(v) * v * Sin(π * v)) // prefix = (z/2)^v * Cot(π * v) / (Γ(v) * v) DoubleX result2 = DoubleX.Zero; double cot = Math2.CotPI(v); if (cot != 0) { double s2 = HypergeometricSeries.Sum0F1(v + 1, -x * x / 4); result2 = powDivGamma * cot * s2 / v; // Do this all in DoubleX } return (result1 - result2); }
/// <summary> /// Returns the Digamma function /// <para>ψ(x) = d/dx(ln(Γ(x))) = Γ'(x)/Γ(x)</para> /// </summary> /// <param name="x">Digamma function argument</param> public static double Digamma(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("Digamma(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Digamma(x: {0}): Requires x != -Infinity", x); return(double.NaN); } return(double.PositiveInfinity); } if (x <= 0) { if (IsInteger(x)) { Policies.ReportPoleError("Digamma(x: {0}) Requires x is not an integer when x <= 0", x); return(double.NaN); } // use the following equations to reflect // ψ(1-x) - ψ(x) = π * cot(π*x) // ψ(-x) = 1/x + ψ(x) + π * cot(π*x) if (x < -1) { return(Digamma(1 - x) - Math.PI * Math2.CotPI(x)); } // The following is the forward recurrence: // -(1/x + 1/(x+1)) + Digamma(x + 2); // with a little less cancellation error near Digamma root: x = -0.50408... return(_Digamma.Rational_1_2(x + 2) - (2 * x + 1) / (x * (x + 1))); } // If we're above the lower-limit for the asymptotic expansion then use it: if (x >= 10) { const double p0 = 0.083333333333333333333333333333333333333333333333333; const double p1 = -0.0083333333333333333333333333333333333333333333333333; const double p2 = 0.003968253968253968253968253968253968253968253968254; const double p3 = -0.0041666666666666666666666666666666666666666666666667; const double p4 = 0.0075757575757575757575757575757575757575757575757576; const double p5 = -0.021092796092796092796092796092796092796092796092796; const double p6 = 0.083333333333333333333333333333333333333333333333333; const double p7 = -0.44325980392156862745098039215686274509803921568627; double xm1 = x - 1; double z = 1 / (xm1 * xm1); double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * (p6 + z * p7)))))); return(Math.Log(xm1) + 1.0 / (2 * xm1) - z * P); } double result = 0; // // If x > 2 reduce to the interval [1,2]: // while (x > 2) { x -= 1; result += 1 / x; } // // If x < 1 use recurrence to shift to > 1: // if (x < 1) { result = -1 / x; x += 1; } result += _Digamma.Rational_1_2(x); return(result); }