/// <summary> /// Returns the sum of a series with each term given by the function f(). /// </summary> /// <param name="f">Delegate returning the next term</param> /// <param name="initialValue">initial value</param> /// <param name="tolerance">Stops when the relative tolerance is reached</param> /// <param name="maxIterations">The maximum number of terms</param> /// <returns> /// The sum of the series or NaN if the series does not converge within the maximum /// number of iterations specified in the policy. /// </returns> public static double Eval(Func <double> f, double initialValue, double tolerance, uint maxIterations) { if (f == null) { Policies.ReportDomainError("Requires f != null"); return(double.NaN); } if (!(tolerance >= 0)) { Policies.ReportDomainError("Requires relative tolerance >= 0"); return(double.NaN); } double sum = initialValue; for (UInt32 i = 0; i < maxIterations; i++) { double prevSum = sum; double delta = f(); sum += delta; if (Math.Abs(delta) <= Math.Abs(prevSum) * tolerance) { return(sum); } } Policies.ReportConvergenceError("Series did not converge in {0} iterations", maxIterations); return(double.NaN); }
/// <summary> /// Optimization of Sum2F1 when a1 == 1. /// <para>Sum2F1_A1 = addend + Σ( (z^k/(b1)_k)/(b2)_k) k={0, ∞}</para> /// </summary> /// <param name="b1"></param> /// <param name="b2"></param> /// <param name="z"></param> /// <param name="addend">The addend to the series</param> /// <returns></returns> private static double Sum1F2_A1(double b1, double b2, double z, double addend) { double term = 1.0; double sum = 1.0 + addend; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= ((z / (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", 1, b1, b2, z, addend, n); return double.NaN; }
// Modified Bessel functions of the first and second kind of fractional order /// <summary> /// Returns (K{v}(x), K{v+1}(x)) /// <para>|x| ≤ 2, the Temme series converges rapidly</para> /// <para>|x| > 2, convergence slows as |x| increases</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> static (double Kv, double Kvp1) K_Temme(double v, double x) { // see: Temme, Journal of Computational Physics, vol 21, 343 (1976) const double tolerance = DoubleLimits.MachineEpsilon; Debug.Assert(Math.Abs(x) <= 2); Debug.Assert(Math.Abs(v) <= 0.5); double gp = Math2.Tgamma1pm1(v); double gm = Math2.Tgamma1pm1(-v); double a = Math.Log(x / 2); double b = Math.Exp(v * a); double sigma = -a * v; double c = Math.Abs(v) < DoubleLimits.MachineEpsilon ? 1 : (Math2.SinPI(v) / (v * Math.PI)); double d = Math2.Sinhc(sigma); double gamma1 = Math.Abs(v) < DoubleLimits.MachineEpsilon ? -Constants.EulerMascheroni : ((0.5 / v) * (gp - gm) * c); double gamma2 = (2 + gp + gm) * c / 2; // initial values double p = (gp + 1) / (2 * b); double q = (1 + gm) * b / 2; double f = (Math.Cosh(sigma) * gamma1 + d * (-a) * gamma2) / c; double h = p; double coef = 1; double sum = coef * f; double sum1 = coef * h; // series summation int k; for (k = 1; k < Policies.MaxSeriesIterations; k++) { f = (k * f + p + q) / (k * k - v * v); p /= k - v; q /= k + v; h = p - k * f; coef *= x * x / (4 * k); sum += coef * f; sum1 += coef * h; if (Math.Abs(coef * f) < Math.Abs(sum) * tolerance) { break; } } if (k >= Policies.MaxSeriesIterations) { Policies.ReportConvergenceError("K_Temme(v:{0}, x:{1}) did not converge after {2} iterations", v, x, Policies.MaxSeriesIterations); return(double.NaN, double.NaN); } return(sum, 2 * sum1 / x); }
/// <summary> /// Returns I{v}(x) using Hankel’s Expansion for large argument. /// <para>If <paramref name="expScale"/> == true, returns e^-x * I{v}(x)</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="expScale"></param> /// <returns></returns> /// <seealso href="http://dlmf.nist.gov/10.40#E1"/> public static double I(double v, double x, bool expScale = false) { // See: http://dlmf.nist.gov/10.40#i double mu = 4 * v * v; double ex = 8 * x; bool ok = false; double sum = 1, lastSum = sum; double sq = 1; double term = 1; int n = 1; for (; n < Policies.MaxSeriesIterations; n++) { // check to see if the series diverges double mult = -((mu - sq * sq) / (n * ex)); if (Math.Abs(mult) >= 1.0) { break; } lastSum = sum; term *= mult; sum += term; sq += 2; if (Math.Abs(term) <= Math.Abs(lastSum) * Policies.SeriesTolerance) { ok = true; break; } } if (!ok) { Policies.ReportConvergenceError("HankelAsym.I(v: {0}, x: {1}): No convergence after {2} iterations", v, x, n); return(double.NaN); } double result; if (expScale) { result = (sum / Math.Sqrt(x)) * Constants.RecipSqrt2PI; } else { // Try to avoid overflow: double e = Math.Exp(x / 2); result = e / Math.Sqrt(x) * Constants.RecipSqrt2PI * sum * e; } return(result); }
/// <summary> /// Returns (Y{v}(x), Y{v+1}(x)) by Temme's method /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> static (double Yv, double Yvp1) Y_Temme(double v, double x) { // see Temme, Journal of Computational Physics, vol 21, 343 (1976) Debug.Assert(Math.Abs(v) <= 0.5); // precondition for using this routine double gp = Math2.Tgamma1pm1(v); double gm = Math2.Tgamma1pm1(-v); double spv = Math2.SinPI(v); double spv2 = Math2.SinPI(v / 2); double xp = Math.Pow(x / 2, v); double a = Math.Log(x / 2); double sigma = -a * v; double d = Math2.Sinhc(sigma); double e = Math.Abs(v) < DoubleLimits.MachineEpsilon ? ((Math.PI * Math.PI / 2) * v) : (2 * spv2 * spv2 / v); double g1 = (v == 0) ? -Constants.EulerMascheroni : ((gp - gm) / ((1 + gp) * (1 + gm) * 2 * v)); double g2 = (2 + gp + gm) / ((1 + gp) * (1 + gm) * 2); double vspv = (Math.Abs(v) < DoubleLimits.MachineEpsilon) ? 1 / Math.PI : v / spv; double f = (g1 * Math.Cosh(sigma) - g2 * a * d) * 2 * vspv; double p = vspv / (xp * (1 + gm)); double q = vspv * xp / (1 + gp); double g = f + e * q; double h = p; double coef = 1; double sum = coef * g; double sum1 = coef * h; double v2 = v * v; double coef_mult = -x * x / 4; int k = 1; for (; k < Policies.MaxSeriesIterations; k++) { f = (k * f + p + q) / (k * k - v2); p /= k - v; q /= k + v; g = f + e * q; h = p - k * g; coef *= coef_mult / k; sum += coef * g; sum1 += coef * h; if (Math.Abs(coef * g) < Math.Abs(sum) * Policies.SeriesTolerance) { return(-sum, -2 * sum1 / x); } } Policies.ReportConvergenceError("Series did not converge after {0} iterations", Policies.MaxSeriesIterations); return(double.NaN, double.NaN); }
/// <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> /// Implements the Exponential Series for integer n /// <para>Sum = ((-z)^(n-1)/(n-1)!) * (ψ(n)-Log(z)) - Σ( (k==n-1) ? 0 : ((-1)^k * z^k)/((k-n+1) * k!)) k={0,∞}</para> /// </summary> /// <param name="n"></param> /// <param name="z"></param> /// <returns></returns> /// <see href="http://functions.wolfram.com/GammaBetaErf/ExpIntegralE/06/01/04/01/02/0005/"/> public static double En_Series(int n, double z) { Debug.Assert(n > 0); const double DefaultTolerance = 2 * DoubleLimits.MachineEpsilon; // the sign of the summation is negative // so negate these double sum = (n > 1) ? -1.0 / (1.0 - n) : 0; double term = -1.0; int k = 1; for (; k < n - 1; k++) { term *= -z / k; if (term == 0) { break; } sum += term / (k - n + 1); } sum += Math.Pow(-z, n - 1) * (Math2.Digamma(n) - Math.Log(z)) / Math2.Factorial(n - 1); if (term == 0) { return(sum); } // skip n-1 term term *= -z / k; for (int i = 0; i < Policies.MaxSeriesIterations; i++) { double prevSum = sum; k++; term *= -z / k; double delta = term / (k - n + 1); sum += delta; if (Math.Abs(delta) <= Math.Abs(prevSum) * DefaultTolerance) { return(sum); } } Policies.ReportConvergenceError("Series did not converge in {0} iterations", Policies.MaxSeriesIterations); return(double.NaN); }
/// <summary> /// Evaluate fv = J{v+1}(x)/J{v}(x) using continued fractions /// <para>|x| <= |v|, function converges rapidly</para> /// <para>|x| > |v|, function needs O(|x|) iterations to converge</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> static (double JRatio, int sign) J_CF1(double v, double x) { // See Abramowitz and Stegun, Handbook of Mathematical Functions, 1972, 9.1.73 // note that the interations required to converge are approximately order(x) const int MaxIterations = 100000; const double tolerance = 2 * DoubleLimits.MachineEpsilon; double tiny = Math.Sqrt(DoubleLimits.MinNormalValue); // modified Lentz's method, see // Lentz, Applied Optics, vol 15, 668 (1976) int s = 1; // sign of denominator double f; double C = f = tiny; double D = 0; int k; for (k = 1; k < MaxIterations; k++) { double a = -1; double b = 2 * (v + k) / x; C = b + a / C; D = b + a * D; if (C == 0) { C = tiny; } if (D == 0) { D = tiny; } D = 1 / D; double delta = C * D; f *= delta; if (D < 0) { s = -s; } if (Math.Abs(delta - 1) < tolerance) { return(-f, s); } } Policies.ReportConvergenceError("Series did not converge after {0} iterations", Policies.MaxSeriesIterations); return(double.NaN, 0); }
static double Amplitude(double v, double x) { // see: http://dlmf.nist.gov/10.18.iii double k = 1; double mu = 4 * v * v; double txq = 4 * x * x; double sq = 1; double term = 1; // The following is the generalized form of the following // as per DLMF: // double sum = 1; // sum += (mu - 1) / (2 * txq); // sum += 3 * (mu - 1) * (mu - 9) / (txq * txq * 8); // sum += 15 * (mu - 1) * (mu - 9) * (mu - 25) / (txq * txq * txq * 8 * 6); double sum = 1, lastSum = sum; for (int n = 0; n < Policies.MaxSeriesIterations; n++) { // check to see when the series starts to diverge double mult = (k / (k + 1.0)) * ((mu - sq * sq) / txq); if (Math.Abs(mult) >= 1.0) { Policies.ReportConvergenceError("Series diverges after {0} iterations", k); return(double.NaN); } lastSum = sum; term *= mult; sum += term; k += 2; sq += 2; if (Math.Abs(term) <= Math.Abs(lastSum) * Policies.SeriesTolerance) { return(Constants.RecipSqrtHalfPI * Math.Sqrt(sum / x)); } } Policies.ReportConvergenceError("Series did not converge after {0} iterations", Policies.MaxSeriesIterations); 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; }
/// <summary> /// Returns the sum of a hypergeometric series 1F1. /// <para>Sum1F0 = addend + Σ( (a1)_n/n! * z^n) n={0,∞}</para> /// </summary> /// <param name="a1">First numerator</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 Sum1F0(double a1, double z, double addend = 0) { if ((double.IsNaN(a1) || double.IsInfinity(a1)) || (double.IsNaN(z) || double.IsInfinity(z)) || double.IsNaN(addend)) { Policies.ReportDomainError("Sum1F0(a1: {0}, z: {1}, addend: {2}): Requires finite arguments", a1, z, addend); return double.NaN; } double sum = 1.0 + addend; if (z == 0) return sum; if (a1 == 1) { if (Math.Abs(z) < 1) return sum - z / (1 - z); // 1-1/(1-z) if (z >= 1) { // Divergent Series return double.PositiveInfinity; } Policies.ReportDomainError("Sum1F0(a1: {0}, z: {1}, addend: {2}): Divergent Series", a1, z, addend); return double.NaN; } double term = 1.0; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= ((a1 + n) / (n + 1)) * z; 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("Sum1F0(a1: {0}, z: {1}, addend: {2}): No convergence after {3} iterations", a1, z, n); return double.NaN; }
/// <summary> /// Evaluate I{v+1}(x) / I{v}(x) using continued fractions /// <para>|x| ≤ |v|, CF1_ik converges rapidly</para> /// <para>|x| > |v|, CF1_ik needs O(|x|) iterations to converge</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> static double I_CF1(double v, double x) { const double tolerance = 2 * DoubleLimits.MachineEpsilon; double f; int k; // See Abramowitz and Stegun, Handbook of Mathematical Functions, 1972, 9.1.73 // modified Lentz's method, see // Lentz, Applied Optics, vol 15, 668 (1976) double tiny = Math.Sqrt(DoubleLimits.MinNormalValue); double C = f = tiny; double D = 0; for (k = 1; k < Policies.MaxSeriesIterations; k++) { double a = 1; double b = 2 * (v + k) / x; C = b + a / C; D = b + a * D; if (C == 0) { C = tiny; } if (D == 0) { D = tiny; } D = 1 / D; double delta = C * D; f *= delta; if (Math.Abs(delta - 1) <= tolerance) { break; } } if (k >= Policies.MaxSeriesIterations) { Policies.ReportConvergenceError("CF1(v:{0}, x:{1}) did not converge after {2} iterations", v, x, Policies.MaxSeriesIterations); return(double.NaN); } return(f); }
/// <summary> /// Series representation for γ(a,x)-1/a for small a /// <para>LowerSmallASeriesSum = Σ((-x)^k/((a+k)*k!)) k={1, inf}</para> /// <para>γ(a,x) = z^a * LowerSmallASeriesSum(a, x, 1/a)</para> /// </summary> /// <param name="a"></param> /// <param name="z"></param> /// <param name="initValue"></param> /// <returns></returns> /// <seealso href="http://functions.wolfram.com/GammaBetaErf/Gamma2/06/01/04/01/01/0003/"/> public static double LowerSmallASeries(double a, double z, double initValue) { double term = 1.0; double sum = initValue; for (int n = 1; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= (-z / n); sum += term / (a + n); if (Math.Abs(term) <= Math.Abs(prevSum) * Policies.SeriesTolerance) { return(sum); } } Policies.ReportConvergenceError("Series did not converge after {0} iterations", Policies.MaxSeriesIterations); return(double.NaN); }
/// <summary> /// Returns the sum of a hypergeometric series 2F0. /// <para>Sum2F0 = addend + Σ( (a1)_n/n! * (a2)_n * z^n) n={0,∞}</para> /// </summary> /// <param name="a1">First numerator</param> /// <param name="a2">Second numerator</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 Sum2F0(double a1, double a2, double z, double addend = 0) { if ((double.IsNaN(a1) || double.IsInfinity(a1)) || (double.IsNaN(a2) || double.IsInfinity(a2)) || (double.IsNaN(z) || double.IsInfinity(z)) || double.IsNaN(addend)) { Policies.ReportDomainError("Sum2F0(a1: {0}, a2: {1}, z: {2}, addend: {3}): Requires arguments", a1, a2, z, addend); return double.NaN; } double sum = 1.0 + addend; if (z == 0) return sum; if (a1 == 1) return Sum2F0_A1(a2, z, addend); if (a2 == 1) return Sum2F0_A1(a1, z, addend); double term = 1.0; int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= z * ((a1 + n) / (n + 1)) * (a2 + 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("Sum2F0(a1: {0}, a2: {1}, z: {2}, addend: {3}): No converge after {4} iterations", a1, a2, z, addend, n); return double.NaN; }
// not used but kept for future reference /// <summary> /// Ei(x) = γ + log(x) + Σ (x^k/(k*k!)) k={1,∞} /// </summary> /// <param name="z"></param> /// <returns></returns> public static double Ei_Series(double z) { const double DefaultTolerance = 2 * DoubleLimits.MachineEpsilon; double term = 1.0; double sum = Math.Log(z) + Constants.EulerMascheroni; for (int n = 1; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= (z / n); sum += term / n; if (Math.Abs(term) <= Math.Abs(prevSum) * DefaultTolerance) { return(sum); } } Policies.ReportConvergenceError("Series did not converge after {0} iterations", Policies.MaxSeriesIterations); return(double.NaN); }
/// <summary> /// Series approximation to the incomplete beta: /// <para>Σ( (1-b)_{k} * x^k/( k! * (a+k) ), k={0, Inf}</para> /// <para>Bx(a, b) = z^a/a * SeriesSum</para> /// <para>Ix(a, b) = z^a/(a*B(a,b)) * SeriesSum</para> /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <param name="x"></param> /// <returns></returns> static double SeriesSum(double a, double b, double x) { const double DefaultTolerance = 2 * DoubleLimits.MachineEpsilon; double sum = 1 / a; double term = 1.0; for (int n = 1; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; term *= (1.0 - b / n) * x; double delta = term / (a + n); sum += delta; if (Math.Abs(delta) <= Math.Abs(prevSum) * DefaultTolerance) { return(sum); } } Policies.ReportConvergenceError("Series did not converge after {0} iterations", Policies.MaxSeriesIterations); return(double.NaN); }
/// <summary> /// Calculate (K{v}(x), K{v+1}(x)) by evaluating continued fraction /// z1 / z0 = U(v+1.5, 2v+1, 2x) / U(v+0.5, 2v+1, 2x) /// <para>|x| >= |v|, CF2_ik converges rapidly</para> /// <para>|x| -> 0, CF2_ik fails to converge</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="expScale">if true, exponentially scale the results</param> /// <returns> /// If <paramref name="expScale"/> is false K{v}(x) and K{v+1}(x); /// otherwise e^x * K{v}(x) and e^x * K{v+1}(x) /// </returns> static (double Kv, double Kvp1) K_CF2(double v, double x, bool expScale) { // See Thompson and Barnett, Computer Physics Communications, vol 47, 245 (1987) const double tolerance = DoubleLimits.MachineEpsilon; double C, f, q, delta; int k; Debug.Assert(Math.Abs(x) > 1); // Steed's algorithm, see Thompson and Barnett, // Journal of Computational Physics, vol 64, 490 (1986) double a = v * v - 0.25; double b = 2 * (x + 1); double D = 1 / b; f = delta = D; // f1 = delta1 = D1, coincidence double prev = 0; double current = 1; double Q = C = -a; double S = 1 + Q * delta; // starting from 2 for (k = 2; k < Policies.MaxSeriesIterations; k++) { // continued fraction f = z1 / z0 a -= 2 * (k - 1); b += 2; D = 1 / (b + a * D); delta *= b * D - 1; f += delta; q = (prev - (b - 2) * current) / a; prev = current; current = q; // forward recurrence for q C *= -a / k; Q += C * q; S += Q * delta; // S converges slower than f if (Math.Abs(Q * delta) < Math.Abs(S) * tolerance) { break; } } if (k >= Policies.MaxSeriesIterations) { Policies.ReportConvergenceError("K_CF2(v:{0}, x:{1}) did not converge after {2} iterations", v, x, Policies.MaxSeriesIterations); return(double.NaN, double.NaN); } double Kv, Kv1; if (expScale) { Kv = (Constants.SqrtHalfPI / Math.Sqrt(x)) / S; } else { Kv = (Constants.SqrtHalfPI / Math.Sqrt(x)) * Math.Exp(-x) / S; } Kv1 = Kv * (0.5 + v + x + (v * v - 0.25) * f) / x; return(Kv, Kv1); }
/// <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> /// p+iq = (J'{v}+iY'{v})/(J{v}+iY{v}) /// <para>|x| >= |v|, function converges rapidly</para> /// <para>|x| -> 0, function fails to converge</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> static (double P, double Q) JY_CF2(double v, double x) { // // This algorithm was originally written by Xiaogang Zhang // using complex numbers to perform the complex arithmetic. // However, that turns out to 10x or more slower than using // all real-valued arithmetic, so it's been rewritten using // real values only. // Debug.Assert(Math.Abs(x) > 1); // modified Lentz's method, complex numbers involved, see // Lentz, Applied Optics, vol 15, 668 (1976) const double tolerance = 2 * DoubleLimits.MachineEpsilon; const double tiny = DoubleLimits.MinNormalValue; double Cr = -0.5 / x, fr = Cr; double Ci = 1, fi = Ci; double v2 = v * v; double a = (0.25 - v2) / x; double br = 2 * x; double bi = 2; double temp = Cr * Cr + 1; Ci = bi + a * Cr / temp; Cr = br + a / temp; double Dr = br; double Di = bi; if (Math.Abs(Cr) + Math.Abs(Ci) < tiny) { Cr = tiny; } if (Math.Abs(Dr) + Math.Abs(Di) < tiny) { Dr = tiny; } temp = Dr * Dr + Di * Di; Dr = Dr / temp; Di = -Di / temp; double delta_r = Cr * Dr - Ci * Di; double delta_i = Ci * Dr + Cr * Di; temp = fr; fr = temp * delta_r - fi * delta_i; fi = temp * delta_i + fi * delta_r; int k; for (k = 2; k < Policies.MaxSeriesIterations; k++) { a = k - 0.5; a *= a; a -= v2; bi += 2; temp = Cr * Cr + Ci * Ci; Cr = br + a * Cr / temp; Ci = bi - a * Ci / temp; Dr = br + a * Dr; Di = bi + a * Di; if (Math.Abs(Cr) + Math.Abs(Ci) < tiny) { Cr = tiny; } if (Math.Abs(Dr) + Math.Abs(Di) < tiny) { Dr = tiny; } temp = Dr * Dr + Di * Di; Dr = Dr / temp; Di = -Di / temp; delta_r = Cr * Dr - Ci * Di; delta_i = Ci * Dr + Cr * Di; temp = fr; fr = temp * delta_r - fi * delta_i; fi = temp * delta_i + fi * delta_r; if (Math.Abs(delta_r - 1) + Math.Abs(delta_i) < tolerance) { break; } } if (k >= Policies.MaxSeriesIterations) { Policies.ReportConvergenceError("JY_CF2(v:{0}, x:{1}) did not converge after {2} iterations", v, x, Policies.MaxSeriesIterations); return(double.NaN, double.NaN); } return(fr, fi); }
/// <summary> /// Returns Log(1 + x) - x with improved accuracy for |x| ≤ 0.5 /// </summary> /// <param name="x">The function argument. Requires x ≥ -1</param> public static double Log1pmx(double x) { if (!(x >= -1)) { Policies.ReportDomainError("Log1pmx(x: {0}): Requires x >= -1", x); return(double.NaN); } if (x == -1) { return(double.NegativeInfinity); } if (double.IsInfinity(x)) { return(double.PositiveInfinity); } // if x is not in [-0.5, 0.625] use the function if (x < -0.5 || x > 0.625) { return(Math.Log(1 + x) - x); } // Log1p works fine if (x < -7.0 / 16) { return(Math2.Log1p(x) - x); } double absX = Math.Abs(x); if (absX < (3.0 / 4) * DoubleLimits.MachineEpsilon) { return(-x * x / 2); } // Based on the techniques used in DiDonato & Morris // Significant Digit Computation of the Incomplete Beta function // Function: Rlog1 // The following taylor series converges very slowly: // log(1+x)-x = -x^2 * (1/2 - x/3 + x^2/4 - x^3/5 + x^4/6 - x^5/7 ... ) // // So, use a combination of preset points and argument reduction: // Step #1: Create preset intervals // let a = preset constant near x // log((1+x)/(1+a)) = log(1+(x-a)/(1+a)) = log(1+u) where u = (x-a)/(1+a); x=a+u+au // log(1+x) = log(1+u) + log(1+a) // log(1+x)-x = log(1+u) + log(1+a) - (a+u+a*u) // log(1+x)-x = (log(1+u)-u) + (log(1+a)-a) - a*u // // Step #2: Argument reduction // Using argument reduction: http://dlmf.nist.gov/4.6#E6 // log(1+u) = 2 * (y + y^3/3 + y^5/5 + y^7/7 ...), where y = u/(2+u) // Thus, u maps to u = 2*y/(1-y) // // log(1 + u) - u // = 2 * y * (1 + y^2/3 + y^4/5 + y^6/7 ...) - 2y/(1-y) // = 2 * y * (1 - 1/(1-y) + y^2/3 + y^4/5 + y^6/7 ...) // = 2 * y * y * (-1/(1-y) + y/3 + y^3/5 + y^5/7 ...) // = -2 * y * y * (1 + u/2 - y*(1/3 + y^2/5 + y^4/7 ...)) // // Step#3: Together // log(1+x)-x = (log(1+a)-a) - a*u - 2 * y * y * (1 + u/2 - y*(1/3 + y^2/5 + y^4/7 ...)) // where // a = an arbitrary point near x // u = (x-a)/(1+a) // y = u/(2+u) #if !USE_GENERAL // Approach for double precision: // With some preset points, // Set u, y for a = 0; double result = 0; double u = x; double y = x / (x + 2); // Set maximum limit for y = +/-0.125 = 1/8 => x = [-2/9, 2/7] = [ -0.2222..., 0.2857... ] const double ymax = 0.125; if (y < -ymax) { // Try to set the magnitude of "a" as low as possible to avoid cancellation errors // but ensure that |(x-a)/(1+a)| < ymax for all x // a, Log[1 + a] - a const double a = -9.0 / 32; const double log1pma = -0.0489916868705768562794077754807; const double minX = (a - ymax * (2 + a)) / (1 + ymax); // ~= -0.44 Debug.Assert(x > minX, "Expecting x > minX, a: " + a + " ymax: " + ymax + " minX: " + minX + " x: " + x); // adjust u, y for a; u = (x - a) / (1 + a); y = u / (u + 2); result = log1pma - a * u; } else if (y > ymax) { // Try to set the magnitude of "a" as low as possible to avoid cancellation errors // but ensure that |(x-a)/(1+a)| < ymax for all x // a, Log[1 + a] - a const double a = 9.0 / 32; const double log1pma = -0.0334138360954187432193972342535; const double maxX = (a + ymax * (2 + a)) / (1 - ymax); // ~= 0.645 Debug.Assert(x < maxX, "Expecting x < maxX, a: " + a + " ymax: " + ymax + " maxX: " + maxX + " x: " + x); // adjust u, y for a; u = (x - a) / (1 + a); y = u / (u + 2); result = log1pma - a * u; } Debug.Assert(Math.Abs(y) <= ymax, "Y too large for the series. x: " + x + " y: " + y); double y2 = y * y; // Set upper limit to y = +/- 0.125 const double c0 = 1.0 / 3; const double c1 = 1.0 / 5; const double c2 = 1.0 / 7; const double c3 = 1.0 / 9; const double c4 = 1.0 / 11; const double c5 = 1.0 / 13; const double c6 = 1.0 / 15; const double c7 = 1.0 / 17; double series = (c0 + y2 * (c1 + y2 * (c2 + y2 * (c3 + y2 * (c4 + y2 * (c5 + y2 * (c6 + y2 * c7))))))); result += -2 * y2 * (1 + u / 2 - y * series); return(result); #else // The generalized approach double y = x / (x + 2); double y2 = y * y; double sum = 1.0 / 3; double mult = y2; double k = 5; for (int n = 1; n < Policies.MaxSeriesIterations; n++) { double prevSum = sum; sum += mult / k; if (prevSum == sum) { return(-2 * y2 * (1 + x / 2 - y * sum)); } mult *= y2; k += 2; } Policies.ReportConvergenceError("Series did not converge after {0} iterations", Policies.MaxSeriesIterations); return(double.NaN); #endif }
/// <summary> /// Returns the Arithmetic-Geometric Mean of a and b /// </summary> /// <param name="a">First Agm argument</param> /// <param name="b">Second Agm argument</param> /// <returns></returns> public static double Agm(double a, double b) { if (double.IsNaN(a) || double.IsNaN(b)) { Policies.ReportDomainError("Agm(a: {0}, b: {1}): NaNs not allowed", a, b); return(double.NaN); } // Agm(x, 0) = Agm(0, x) = 0 if (a == 0 || b == 0) { return(0); } // We don't handle complex results if (Math.Sign(a) != Math.Sign(b)) { Policies.ReportDomainError("Agm(a: {0}, b: {1}): Requires a, b have the same sign", a, b); return(double.NaN); } // Agm(± a, ± ∞) = ± ∞ if (double.IsInfinity(a)) { return(a); } if (double.IsInfinity(b)) { return(b); } if (a == b) { return(a); //or b } // save a, b for error messages double aOriginal = a; double bOriginal = b; // Agm(-a, -b) = -Agm(a, b) double mult = 1; if (a < 0) { a = -a; b = -b; mult = -1; } // Set a > b: Agm(a, b) == Agm(b, a) if (a < b) { Utility.Swap(ref a, ref b); } // If b/a < eps^(1/4), we can transform Agm(a, b) to a * Agm(1, b/a) // and use the Taylor series Agm(1, y) at y = 0 // See: http://functions.wolfram.com/EllipticFunctions/ArithmeticGeometricMean/06/01/01/ double y = b / a; if (y <= DoubleLimits.RootMachineEpsilon._4) { // Compute log((b/a)/4) being careful not to underflow on b/a double logyDiv4; if (y < 4 * DoubleLimits.MinNormalValue) { logyDiv4 = (y < DoubleLimits.MinNormalValue) ? Math.Log(b) - Math.Log(a) : Math.Log(y); logyDiv4 -= 2 * Constants.Ln2; } else { logyDiv4 = Math.Log(y / 4); } double r = (Math.PI / 8) * (-4.0 + (1 + 1 / logyDiv4) * y * y) / logyDiv4; double result = mult * a * r; return(result); } // scale by a to guard against overflows/underflows in a*b mult *= a; b = y; a = 1; // Use a series of arithmetic and geometric means. int n = 0; for (; n < Policies.MaxSeriesIterations; n++) { #if !USE_GENERAL // If |b/a-1| < eps^(1/9), we can transform Agm(a, b) to a*Agm(1, b/a) // and use the Taylor series Agm(1, z) at z = 1: // Agm(1, z) = 1 + (z-1)/2 - (z-1)^2/16 + .... // http://functions.wolfram.com/EllipticFunctions/ArithmeticGeometricMean/06/01/02/0001/ double z = (b - a); if (Math.Abs(z) < a * DoubleLimits.RootMachineEpsilon._9) { const double c0 = 1; const double c1 = 1.0 / 2; const double c2 = -1.0 / 16; const double c3 = 1.0 / 32; const double c4 = -21.0 / 1024; const double c5 = 31.0 / 2048; const double c6 = -195.0 / 16384; const double c7 = 319.0 / 32768; const double c8 = -34325.0 / 4194304; z /= a; // z=(b-a)/a = b/a-1 double r = c0 + z * (c1 + z * (c2 + z * (c3 + z * (c4 + z * (c5 + z * (c6 + z * (c7 + z * c8))))))); double result = mult * a * r; return(result); } // take a step double nextA = 0.5 * (a + b); b = Math.Sqrt(a * b); a = nextA; #else // the double specialized series above is a little faster, // so this more general approach is disabled // tol = Sqrt(8 * eps) const double tol = 2 * Constants.Sqrt2 * DoubleLimits.RootMachineEpsilon._2; double nextA = 0.5 * (a + b); if (Math.Abs(b - a) < a * tol) { return(mult * nextA); } // take a step b = Math.Sqrt(a * b); a = nextA; #endif } Policies.ReportConvergenceError("Agm(a: {0}, b: {1}): Did not converge after {2} iterations", aOriginal, bOriginal, n); return(double.NaN); }