/// <summary> /// Returns the sum of a hypergeometric series 3F1. /// <para>Sum3F1 = addend + Σ(( (a1)_n * (a2)_n * (a3)_n)/(b1)_n * (z^n/n!)) n={0,∞}</para> /// </summary> /// <param name="a1">First numerator</param> /// <param name="a2">Second numerator</param> /// <param name="a3">Third numerator</param> /// <param name="b1">First denominator. Requires b1 > 0</param> /// <param name="z">Argument</param> /// <param name="addend">The addend to the series</param> /// <returns> /// The sum of the series, or NaN if the series does not converge /// within the policy maximum number of iterations /// </returns> public static double Sum3F1(double a1, double a2, double a3, double b1, double z, double addend = 0) { if ((double.IsNaN(a1) || double.IsInfinity(a1)) || (double.IsNaN(a2) || double.IsInfinity(a2)) || (double.IsNaN(a3) || double.IsInfinity(a3)) || (double.IsNaN(z) || double.IsInfinity(z)) || (double.IsNaN(b1) || double.IsInfinity(b1)) || double.IsNaN(addend)) { Policies.ReportDomainError("Sum3F1(a1: {0}, a2: {1}, a3: {2}, b1: {3}, z: {4}, addend: {5}): Requires finite arguments", a1, a2, a3, b1, z, addend); return double.NaN; } if (b1 <= 0 && Math2.IsInteger(b1)) { Policies.ReportDomainError("Sum3F1(a1: {0}, a2: {1}, a3: {2}, b1: {3}, z: {4}, addend: {5}): Requires b1 is not zero or a negative integer", a1, a2, a3, b1, z, addend); return double.NaN; } double sum = 1.0 + addend; if (z == 0) return sum; if (a1 == b1) return Sum2F0(a2, a3, z, addend); if (a2 == b1) return Sum2F0(a1, a3, z, addend); if (a3 == b1) return Sum2F0(a1, a2, z, addend); #if false // TODO: if (a1 == 1) return Sum3F1_A1(a2, a3, b1, z, addend); if (a2 == 1) return Sum3F1_A1(a1, a3, b1, z, addend); if (a3 == 1) return Sum3F1_A1(a1, a2, b1, z, addend); #endif double term = 1.0; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= (z / (n + 1)) * ((a1 + n) / (b1 + n)) * (a2 + n) * (a3 + n); sum += term; #if CMP_FLOAT if (Math.Abs(term) <= Math.Abs(prevSum) * Policies.SeriesTolerance) return sum; #else if (sum == prevSum) return sum; #endif } Policies.ReportConvergenceError("Sum3F1(a1: {0}, a2: {1}, a3: {2}, b1: {3}, z: {4}, addend: {5}): No convergence after {6} iterations", a1, a2, a3, b1, z, addend, n); return double.NaN; }
/// <summary> /// Returns the sum of a hypergeometric series 1F2. /// <para>Sum2F1 = addend + Σ( ((a1)_n)/((b1)_n * (b2)_n) * (z^n/n!)) n={0,∞}</para> /// </summary> /// <param name="a1">First numerator</param> /// <param name="b1">First denominator. Requires b1 > 0</param> /// <param name="b2">Second denominator. Requires b2 > 0</param> /// <param name="z">Argument</param> /// <param name="addend">The addend to the series</param> /// <returns> /// The sum of the series, or NaN if the series does not converge /// within the policy maximum number of iterations /// </returns> public static double Sum1F2(double a1, double b1, double b2, double z, double addend = 0) { if ((double.IsNaN(a1) || double.IsInfinity(a1)) || (double.IsNaN(z) || double.IsInfinity(z)) || (double.IsNaN(b1) || double.IsInfinity(b1)) || (double.IsNaN(b2) || double.IsInfinity(b2)) || double.IsNaN(addend)) { Policies.ReportDomainError("Sum1F2(a1: {0}, b1: {1}, b2: {2}, z: {3}, addend: {4}): Requires finite arguments", a1, b1, b2, z, addend); return double.NaN; } if ((b1 <= 0 && Math2.IsInteger(b1)) || (b2 <= 0 && Math2.IsInteger(b2))) { Policies.ReportDomainError("Sum1F2(a1: {0}, b1: {1}, b2: {2}, z: {3}, addend: {4}): Requires b1, b2 are not zero or negative integers", a1, b1, b2, z, addend); return double.NaN; } double sum = 1.0 + addend; if (z == 0) return sum; if (a1 == b1) return Sum0F1(b2, z, addend); if (a1 == b2) return Sum0F1(b1, z, addend); if (a1 == 1) return Sum1F2_A1(b1, b2, z, addend); double term = 1.0; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= (z / (n + 1)) * (((a1 + n) / (b1 + n)) / (b2 + n)); sum += term; #if CMP_FLOAT if (Math.Abs(term) <= Math.Abs(prevSum) * Policies.SeriesTolerance) return sum; #else if (sum == prevSum) return sum; #endif } Policies.ReportConvergenceError("Sum1F2(a1: {0}, b1: {1}, b2: {2}, z: {3}, addend: {4}): No convergence after {0} iterations", a1, b1, b2, z, addend, n); return double.NaN; }
// // 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 sum of a hypergeometric series PFQ, which has a variable number of numerators and denominators. /// <para> PFQ = addend + Σ(( Prod(a[j]_n)/Prod(b[j])_n * (z^n/n!)) n={0,∞}</para> /// </summary> /// <param name="a">The array of numerators</param> /// <param name="b">The array of denominators. (b != 0 or b != negative integer)</param> /// <param name="z">Argument</param> /// <param name="addend">The addend to the series</param> /// <returns> /// The sum of the series, or NaN if the series does not converge /// within the policy maximum number of iterations /// </returns> public static double SumPFQ(double[] a, double[] b, double z, double addend = 0) { if ( (a == null || b == null) || (double.IsNaN(z) || double.IsInfinity(z)) || double.IsNaN(addend)) { string aStr = (a == null) ? "null" : a.ToString(); string bStr = (b == null) ? "null" : b.ToString(); Policies.ReportDomainError("SumPFQ(a: {0}; b: {1}, z: {2}, addend: {3}): Requires finite non-null arguments", aStr, bStr, z, addend); return double.NaN; } if (a.Length == 0 && b.Length == 0) return Sum0F0(z, addend); // check for infinities or non-positive integer b bool hasBadParameter = false; for (int i = 0; i < a.Length; i++) { if (double.IsInfinity(a[i])) { hasBadParameter = true; break; } } if (!hasBadParameter) { for (int i = 0; i < b.Length; i++) { if (double.IsInfinity(b[i]) || (b[i] <= 0 && Math2.IsInteger(b[i]))) { hasBadParameter = true; break; } } } if (hasBadParameter) { Policies.ReportDomainError("SumPFQ(a: {0}; b: {1}, z: {2}, addend: {3}): Requires finite arguments and b not zero or a negative integer", a, b, z, addend); return double.NaN; } double sum = 1.0 + addend; if (z == 0) return sum; double term = 1.0; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double term_part = (z / (n + 1)); // use a/b where we can for max(a.Length, b.Length) int j = 0; for (; j < a.Length && j < b.Length; j++) { term_part *= (a[j] + n) / (b[j] + n); } // otherwise multiply or divide the rest of the factors // either a or b for (; j < a.Length; j++) { term_part *= (a[j] + n); } for (; j < b.Length; j++) { term_part /= (b[j] + n); } term *= term_part; double prevSum = sum; sum += term; #if CMP_FLOAT if (Math.Abs(term) <= Math.Abs(prevSum) * Policies.SeriesTolerance) return sum; #else if (sum == prevSum) return sum; #endif } Policies.ReportConvergenceError("No convergence after {0} iterations", n); return double.NaN; }
/// <summary> /// Simultaneously compute I{v}(x) and K{v}(x) /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needI"></param> /// <param name="needK"></param> /// <returns>A tuple of (I, K). If needI == false or needK == false, I = NaN or K = NaN respectively</returns> public static (double I, double K) IK(double v, double x, bool needI, bool needK) { Debug.Assert(needI || needK, "NeedI and NeedK cannot both be false"); // set initial values for parameters double I = double.NaN; double K = double.NaN; // check v first so that there are no integer throws later if (Math.Abs(v) > int.MaxValue) { Policies.ReportNotImplementedError("BesselIK(v: {0}, x: {1}): Requires |v| <= {2}", v, x, int.MaxValue); return(I, K); } if (v < 0) { v = -v; // for integer orders only, we can use the following identities: // I{-n}(x) = I{n}(v) // K{-n}(x) = K{n}(v) // for any v, use reflection rule: // I{-v}(x) = I{v}(x) + (2/PI) * sin(pi*v)*K{v}(x) // K{-v}(x) = K{v}(x) if (needI && !Math2.IsInteger(v)) { var(IPos, KPos) = IK(v, x, true, true); I = IPos + (2 / Math.PI) * Math2.SinPI(v) * KPos; if (needK) { K = KPos; } return(I, K); } } if (x < 0) { Policies.ReportDomainError("BesselIK(v: {0}, x: {1}): Complex result not supported. Requires x >= 0", v, x); return(I, K); } // both x and v are non-negative from here Debug.Assert(x >= 0 && v >= 0); if (x == 0) { if (needI) { I = (v == 0) ? 1.0 : 0.0; } if (needK) { K = double.PositiveInfinity; } return(I, K); } if (needI && (x < 2 || 3 * (v + 1) > x * x)) { I = I_SmallArg(v, x); needI = false; if (!needK) { return(I, K); } } // Hankel is fast, and reasonably accurate. // at x == 32, it will converge in about 15 iterations if (x >= HankelAsym.IKMinX(v)) { if (needK) { K = HankelAsym.K(v, x); } if (needI) { I = HankelAsym.I(v, x); } return(I, K); } // the uniform expansion is here as a last resort // to limit the number of recurrences, but it is less accurate. if (UniformAsym.IsIKAvailable(v, x)) { if (needK) { K = UniformAsym.K(v, x); } if (needI) { I = UniformAsym.I(v, x); } return(I, K); } // K{v}(x) and K{v+1}(x), binary scaled var(Kv, Kv1, binaryScale) = K_CF(v, x); if (needK) { K = Math2.Ldexp(Kv, binaryScale); } if (needI) { // use the Wronskian relationship // Since CF1 is O(x) for x > v, try to weed out obvious overflows // Note: I{v+1}(x)/I{v}(x) is in [0, 1]. // I{0}(713. ...) == MaxDouble const double I0MaxX = 713; if (x > I0MaxX && x > v) { I = Math2.Ldexp(1.0 / (x * (Kv + Kv1)), -binaryScale); if (double.IsInfinity(I)) { return(I, K); } } double W = 1 / x; double fv = I_CF1(v, x); I = Math2.Ldexp(W / (Kv * fv + Kv1), -binaryScale); } return(I, K); }
/// <summary> /// Returns J{v}(x) for integer v /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static double JN(double v, double x) { Debug.Assert(Math2.IsInteger(v)); // For integer orders only, we can use two identities: // #1: J{-n}(x) = (-1)^n * J{n}(x) // #2: J{n}(-x) = (-1)^n * J{n}(x) double sign = 1; if (v < 0) { v = -v; if (Math2.IsOdd(v)) { sign = -sign; } } if (x < 0) { x = -x; if (Math2.IsOdd(v)) { sign = -sign; } } Debug.Assert(v >= 0 && x >= 0); if (v > int.MaxValue) { Policies.ReportNotImplementedError("BesselJ(v: {0}): Large integer values not yet implemented", v); return(double.NaN); } int n = (int)v; // // Special cases: // if (n == 0) { return(sign * J0(x)); } if (n == 1) { return(sign * J1(x)); } // n >= 2 Debug.Assert(n >= 2); if (x == 0) { return(0); } if (x < 5 || (v > x * x / 4)) { return(sign * J_SmallArg(v, x)); } // if v > MinAsymptoticV, use the asymptotics const int MinAsymptoticV = 7; // arbitrary constant to reduce iterations if (v > MinAsymptoticV) { double Jv; DoubleX Yv; if (JY_TryAsymptotics(v, x, out Jv, out Yv, true, false)) { return(sign * Jv); } } // v < abs(x), forward recurrence stable and usable // v >= abs(x), forward recurrence unstable, use Miller's algorithm if (v < x) { // use forward recurrence double prev = J0(x); double current = J1(x); return(sign * Recurrence.ForwardJY(1.0, x, n - 1, current, prev).JYvpn); } else { // Steed is somewhat more accurate when n gets large if (v >= 200) { var(J, Y) = JY_Steed(n, x, true, false); return(J); } // J{n+1}(x) / J{n}(x) var(fv, s) = J_CF1(n, x); var(Jvmn, Jvmnp1, scale) = Recurrence.BackwardJY_B(n, x, n, s, fv * s); // scale the result double Jv = (J0(x) / Jvmn); Jv = Math2.Ldexp(Jv, -scale); return((s * sign) * Jv); // normalization } }
/// <summary> /// Compute J{v}(x) and Y{v}(x) /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needJ"></param> /// <param name="needY"></param> /// <returns></returns> public static (double J, DoubleX Y) JY(double v, double x, bool needJ, bool needY) { Debug.Assert(needJ || needY); // uses Steed's method // see Barnett et al, Computer Physics Communications, vol 8, 377 (1974) // set [out] parameters double J = double.NaN; DoubleX Y = DoubleX.NaN; // check v first so that there are no integer throws later if (Math.Abs(v) > int.MaxValue) { Policies.ReportNotImplementedError("BesselJY(v: {0}): Large |v| > int.MaxValue not yet implemented", v); return(J, Y); } if (v < 0) { v = -v; if (Math2.IsInteger(v)) { // for integer orders only, we can use the following identities: // J{-n}(x) = (-1)^n * J{n}(x) // Y{-n}(x) = (-1)^n * Y{n}(x) if (Math2.IsOdd(v)) { var(JPos, YPos) = JY(v, x, needJ, needY); if (needJ) { J = -JPos; } if (needY) { Y = -YPos; } return(J, Y); } } if (v - Math.Floor(v) == 0.5) { Debug.Assert(v >= 0); // use reflection rule: // for integer m >= 0 // J{-(m+1/2)}(x) = (-1)^(m+1) * Y{m+1/2}(x) // Y{-(m+1/2)}(x) = (-1)^m * J{m+1/2}(x) // call the general bessel functions with needJ and needY reversed var(JPos, YPos) = JY(v, x, needY, needJ); double m = v - 0.5; bool isOdd = Math2.IsOdd(m); if (needJ) { double y = (double)YPos; J = isOdd ? y : -y; } if (needY) { Y = isOdd ? -JPos : JPos; } return(J, Y); } else { // use reflection rule: // J{-v}(x) = cos(pi*v)*J{v}(x) - sin(pi*v)*Y{v}(x) // Y{-v}(x) = sin(pi*v)*J{v}(x) + cos(pi*v)*Y{v}(x) var(JPos, YPos) = JY(v, x, true, true); double cp = Math2.CosPI(v); double sp = Math2.SinPI(v); J = cp * JPos - (double)(sp * YPos); Y = sp * JPos + cp * YPos; return(J, Y); } } // both x and v are positive from here Debug.Assert(x >= 0 && v >= 0); if (x == 0) { // For v > 0 if (needJ) { J = 0; } if (needY) { Y = DoubleX.NegativeInfinity; } return(J, Y); } int n = (int)Math.Floor(v + 0.5); double u = v - n; // -1/2 <= u < 1/2 // is it an integer? if (u == 0) { if (v == 0) { if (needJ) { J = J0(x); } if (needY) { Y = Y0(x); } return(J, Y); } if (v == 1) { if (needJ) { J = J1(x); } if (needY) { Y = Y1(x); } return(J, Y); } // for integer order only if (needY && x < DoubleLimits.RootMachineEpsilon._2) { Y = YN_SmallArg(n, x); if (!needJ) { return(J, Y); } needY = !needY; } } if (needJ && ((x < 5) || (v > x * x / 4))) { // always use the J series if we can J = J_SmallArg(v, x); if (!needY) { return(J, Y); } needJ = !needJ; } if (needY && x <= 2) { // J should have already been solved above Debug.Assert(!needJ); // Evaluate using series representations. // Much quicker than Y_Temme below. // This is particularly important for x << v as in this // area Y_Temme may be slow to converge, if it converges at all. // for non-integer order only if (u != 0) { if ((x < 1) && (Math.Log(DoubleLimits.MachineEpsilon / 2) > v * Math.Log((x / 2) * (x / 2) / v))) { Y = Y_SmallArgSeries(v, x); return(J, Y); } } // Use Temme to find Yu where |u| <= 1/2, then use forward recurrence for Yv var(Yu, Yu1) = Y_Temme(u, x); var(Yvpn, Yvpnm1, YScale) = Recurrence.ForwardJY_B(u + 1, x, n, Yu1, Yu); Y = DoubleX.Ldexp(Yvpnm1, YScale); return(J, Y); } Debug.Assert(x > 2 && v >= 0); // Try asymptotics directly: if (x > v) { // x > v*v if (x >= HankelAsym.JYMinX(v)) { var result = HankelAsym.JY(v, x); if (needJ) { J = result.J; } if (needY) { Y = result.Y; } return(J, Y); } // Try Asymptotic Phase for x > 47v if (x >= MagnitudePhase.MinX(v)) { var result = MagnitudePhase.BesselJY(v, x); if (needJ) { J = result.J; } if (needY) { Y = result.Y; } return(J, Y); } } // fast and accurate within a limited range of v ~= x if (UniformAsym.IsJYPrecise(v, x)) { var(Jv, Yv) = UniformAsym.JY(v, x); if (needJ) { J = Jv; } if (needY) { Y = Yv; } return(J, Y); } // Try asymptotics with recurrence: if (x > v && x >= HankelAsym.JYMinX(v - Math.Floor(v) + 1)) { var(Jv, Yv) = JY_AsymRecurrence(v, x, needJ, needY); if (needJ) { J = Jv; } if (needY) { Y = Yv; } return(J, Y); } // Use Steed's Method var(SteedJv, SteedYv) = JY_Steed(v, x, needJ, needY); if (needJ) { J = SteedJv; } if (needY) { Y = SteedYv; } return(J, Y); }