/// <summary> /// Returns the ratio of gamma functions /// <para>TgammaRatio(x,y) = Γ(x)/Γ(y)</para> /// </summary> /// <param name="x">The argument for the gamma function in the numerator. Requires x > 0</param> /// <param name="y">The argument for the gamma function in the denominator. Requires y > 0</param> public static double TgammaRatio(double x, double y) { if (!(x > 0) || !(y > 0)) { Policies.ReportDomainError("TgammaRatio(x: {0}, y: {1}): Requires x > 0, y > 0; negative arguments not implemented", x, y); return double.NaN; } if (double.IsInfinity(x)) { if (double.IsInfinity(y)) { Policies.ReportDomainError("TgammaRatio(x: {0}, y: {1}): Infinity/Infinity", x, y); return double.NaN; } return double.PositiveInfinity; } if (double.IsInfinity(y)) return 0.0; // if we're not going to overflow, take the direct approach if (x <= DoubleLimits.MaxGamma && y <= DoubleLimits.MaxGamma) { // need to protect against denormalized numbers, so // use the first term of the power series: Gamma[x] ~= 1/x - Euler ... const double LowerLimit = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if (x < LowerLimit) { if (y < LowerLimit) return y / x; // careful: Tgamma(y) can be < 1, x can be denorm or Min Normal double result = 1 / (x * Tgamma(y)); return result; } if (y < LowerLimit) { double result = y * Tgamma(x); return result; } return Tgamma(x) / Tgamma(y); } // Handle the case where we have one large argument and one small argument Debug.Assert(x > DoubleLimits.MaxGamma || y > DoubleLimits.MaxGamma); if (x <= DoubleLimits.MaxGamma) { // At max values // min denorm: Γ(2^-1074)/Γ(170) ~= 4.7e18 // min norm: Γ(2^-1022)/Γ(170) ~= 1052.74 // Γ(1)/Γ(170) ~= 2.3e-305 // use duplication // Γ(2z) = 2^(2z-1)/Sqrt(Pi) * Γ(z) * Γ(z+1/2) if ( y <= 2 * DoubleLimits.MaxGamma - 1) { var t1 = Tgamma(y / 2); var t2 = Math.Pow(2, y) / (2 * Constants.SqrtPI); var t3 = Tgamma(y / 2 + 0.5); if (x < DoubleLimits.MachineEpsilon/Constants.EulerMascheroni ) { return 1.0/(x * t1 * t2 * t3); } else { return Tgamma(x)/ t1 / t2 / t3; } } // this will underflow return Math.Exp(Lgamma(x) - Lgamma(y)); } if (y <= DoubleLimits.MaxGamma) { // At max values // min denorm: Γ(170)/Γ(2^-1074) ~= 2.1e-19 // min norm Γ(170)/Γ(2^-1022) ~= 9.5e-4 // Γ(170)/Γ(1) ~= 4.3e304 // use duplication // Γ(2z) = 2^(2z-1)/Sqrt(Pi) * Γ(z) * Γ(z+1/2) if (y <= 2 * DoubleLimits.MaxGamma - 1) { var t1 = Tgamma(x / 2); var t2 = Math.Pow(2, x) / (2 * Constants.SqrtPI); var t3 = Tgamma(x / 2 + 0.5); if (y < DoubleLimits.MachineEpsilon / Constants.EulerMascheroni) { return (y * t1 * t2 * t3); } else { return t1/Tgamma(y) * t2 * t3; } } // this will overflow return Math.Exp(Lgamma(x) - Lgamma(y)); } // Regular case, x and y both large and similar in magnitude: return TgammaDeltaRatio(x, y - x); }
/// <summary> /// Returns the value of the first derivative to the Airy Ai function at <paramref name="x"/> /// </summary> /// <param name="x">The function argument</param> /// <returns></returns> public static double AiryAiPrime(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("AiryAiPrime(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x > 0) { return(0); } Policies.ReportDomainError("AiryAiPrime(x: {0}): Requires x != -Infinity", x); return(double.NaN); } double absX = Math.Abs(x); if (x >= -3 && x <= 2) { // Use small series. See: // http://functions.wolfram.com/Bessel-TypeFunctions/AiryAiPrime/26/01/01/ // http://dlmf.nist.gov/9.4 double z = x * x * x / 9; double s1 = x * x * (Constants.AiryAi0 / 2) * HypergeometricSeries.Sum0F1(5 / 3.0, z); double s2 = Constants.AiryAiPrime0 * HypergeometricSeries.Sum0F1(1 / 3.0, z); return(s1 + s2); } const double v = 2.0 / 3; double zeta = 2 * absX * Math.Sqrt(absX) / 3; if (x < 0) { if (x < -32) { return(AiryAsym.AiBiPrimeNeg(x).Ai); } // The following is //double j1 = Math2.BesselJ(v, zeta); //double j2 = Math2.BesselJ(-v, zeta); //double aip = -x * (j1 - j2) / 3; var(J, Y) = _Bessel.JY(v, zeta, true, true); double s = 0.5 * (J + ((double)Y) / Constants.Sqrt3); double aip = -x * s; return(aip); } else { if (x >= 16) { return(AiryAsym.AiPrime(x)); } double aip = -Math2.BesselK(v, zeta) * x / (Constants.Sqrt3 * Math.PI); return(aip); } }
/// <summary> /// Evaluates a rational polynomial with the given argument /// <para>(num[0] + num[1]*x + num[2]*x^2 ...)/(denom[0] + denom[1]*x + denom[2]*x^2 ...)</para> /// </summary> /// <param name="num">The numerator coefficient array</param> /// <param name="denom">The denominator coefficient array</param> /// <param name="x">The argument</param> /// <remarks> /// When numerator and denominator are equal in order there /// are some tricks we can use to prevent overflow that might otherwise /// occur in direct polynomial evaluation, for example if <paramref name="x"/> is large /// </remarks> public static double EvalRational(double[] num, double[] denom, double x) { // Uses Horner's Method // see http://en.wikipedia.org/wiki/Horner_scheme if (num == null) { Policies.ReportDomainError("Requires num != null"); return(double.NaN); } if (denom == null) { Policies.ReportDomainError("Requires denom != null"); return(double.NaN); } if (denom.Length == 0) { Policies.ReportDomainError("Divide by zero. Requires denom != 0"); return(double.NaN); } if (num.Length == 0) { return(0); } if (num.Length != denom.Length) { return(Polynomial.Eval(num, x) / Polynomial.Eval(denom, x)); } if (Math.Abs(x) <= 1) { double z = x; int n = num.Length - 1; double numX = num[n]; double denomX = denom[n]; for (int i = n - 1; i >= 0; i--) { numX = numX * z + num[i]; denomX = denomX * z + denom[i]; } return(numX / denomX); } else { // reverse the order // (1 + 1/x) / (2 + 2/x) = (x+1)/(2x+2) // can only do this when num.Length == denom.Length double z = 1 / x; double numX = num[0]; double denomX = denom[0]; for (int i = 1; i < num.Length; ++i) { numX = numX * z + num[i]; denomX = denomX * z + denom[i]; } return(numX / denomX); } }
/// <summary> /// Returns Y{0}(x) /// </summary> /// <param name="x"></param> /// <returns></returns> public static double Y0(double x) { if (x < 0) { Policies.ReportDomainError("BesselY(v: 0, x: {0}): Requires x >= 0; complex number result not supported", x); return(double.NaN); } if (x == 0) { Policies.ReportPoleError("BesselY(v: 0, x: {0}): Overflow", x); return(double.NegativeInfinity); } if (double.IsInfinity(x)) { return(0); } double value, factor; if (x < DoubleLimits.MachineEpsilon) { return((2 / Math.PI) * (Math.Log(x / 2) + Constants.EulerMascheroni)); } // Bessel function of the second kind of order zero // x <= 8, minimax rational approximations on root-bracketing intervals // x > 8, Hankel asymptotic expansion in Hart, Computer Approximations, 1968 if (x <= 3) // x in [eps, 3] { const double Zero1 = 8.9357696627916752158e-01; const double Zero1a = 3837883847.0 / 4294967296; const double Zero1b = -8.66317862382940502854117587748531380699855129307710548e-11; const double p0 = 1.0723538782003176831e+11; const double p1 = -8.3716255451260504098e+09; const double p2 = 2.0422274357376619816e+08; const double p3 = -2.1287548474401797963e+06; const double p4 = 1.0102532948020907590e+04; const double p5 = -1.8402381979244993524e+01; const double q0 = 5.8873865738997033405e+11; const double q1 = 8.1617187777290363573e+09; const double q2 = 5.5662956624278251596e+07; const double q3 = 2.3889393209447253406e+05; const double q4 = 6.6475986689240190091e+02; const double q5 = 1.0; double z = x * x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * q5)))); double y = (2 / Math.PI) * Math.Log(x / Zero1) * _Bessel.J0(x); factor = (x + Zero1) * ((x - Zero1a) - Zero1b); value = y + factor * (P / Q); } else if (x <= 5.5) // x in (3, 5.5] { const double Zero2 = 3.9576784193148578684e+00; const double Zero2a = 16998099379.0 / 4294967296; const double Zero2b = 9.846238317388612698651281418603765563630625507511794841e-12; const double p0 = -2.2213976967566192242e+13; const double p1 = -5.5107435206722644429e+11; const double p2 = 4.3600098638603061642e+10; const double p3 = -6.9590439394619619534e+08; const double p4 = 4.6905288611678631510e+06; const double p5 = -1.4566865832663635920e+04; const double p6 = 1.7427031242901594547e+01; const double q0 = 4.3386146580707264428e+14; const double q1 = 5.4266824419412347550e+12; const double q2 = 3.4015103849971240096e+10; const double q3 = 1.3960202770986831075e+08; const double q4 = 4.0669982352539552018e+05; const double q5 = 8.3030857612070288823e+02; const double q6 = 1.0; double z = x * x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * p6))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * q6))))); double y = (2 / Math.PI) * Math.Log(x / Zero2) * _Bessel.J0(x); factor = (x + Zero2) * ((x - Zero2a) - Zero2b); value = y + factor * (P / Q); } else if (x <= 8) // x in (5.5, 8] { const double Zero3 = 7.0860510603017726976e+00; const double Zero3a = 15217178781.0 / 2147483648; const double Zero3b = -5.07017534414388797421475310284896188222355301448323476e-11; const double p0 = -8.0728726905150210443e+15; const double p1 = 6.7016641869173237784e+14; const double p2 = -1.2829912364088687306e+11; const double p3 = -1.9363051266772083678e+11; const double p4 = 2.1958827170518100757e+09; const double p5 = -1.0085539923498211426e+07; const double p6 = 2.1363534169313901632e+04; const double p7 = -1.7439661319197499338e+01; const double q0 = 3.4563724628846457519e+17; const double q1 = 3.9272425569640309819e+15; const double q2 = 2.2598377924042897629e+13; const double q3 = 8.6926121104209825246e+10; const double q4 = 2.4727219475672302327e+08; const double q5 = 5.3924739209768057030e+05; const double q6 = 8.7903362168128450017e+02; const double q7 = 1.0; double z = x * x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * (p6 + z * p7)))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * q7)))))); double y = (2 / Math.PI) * Math.Log(x / Zero3) * _Bessel.J0(x); factor = (x + Zero3) * ((x - Zero3a) - Zero3b); value = y + factor * (P / Q); } else // x in (8, infinity) { double y = 8 / x; double z = y * y; double rc, rs; { const double p0 = 2.2779090197304684302e+04; const double p1 = 4.1345386639580765797e+04; const double p2 = 2.1170523380864944322e+04; const double p3 = 3.4806486443249270347e+03; const double p4 = 1.5376201909008354296e+02; const double p5 = 8.8961548424210455236e-01; const double q0 = 2.2779090197304684318e+04; const double q1 = 4.1370412495510416640e+04; const double q2 = 2.1215350561880115730e+04; const double q3 = 3.5028735138235608207e+03; const double q4 = 1.5711159858080893649e+02; const double q5 = 1.0; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * q5)))); rc = P / Q; } { const double p0 = -8.9226600200800094098e+01; const double p1 = -1.8591953644342993800e+02; const double p2 = -1.1183429920482737611e+02; const double p3 = -2.2300261666214198472e+01; const double p4 = -1.2441026745835638459e+00; const double p5 = -8.8033303048680751817e-03; const double q0 = 5.7105024128512061905e+03; const double q1 = 1.1951131543434613647e+04; const double q2 = 7.2642780169211018836e+03; const double q3 = 1.4887231232283756582e+03; const double q4 = 9.0593769594993125859e+01; const double q5 = 1.0; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * q5)))); rs = P / Q; } // The following code is: //double z = x - 0.25f * Math.PI; //value = (Constants.RecipSqrtHalfPI / Math.Sqrt(x)) * (rc * Math.Sin(z) + y * rs * Math.Cos(z)); // Which can be written as: // (double, double) sincos = Math2.SinCos(x,-0.25); // value = (Constants.RecipSqrtHalfPI / Math.Sqrt(x)) * (rc * sincos.Item1 + y * rs * sincos.Item2); // Or, using trig addition rules, simplified to: value = (Constants.RecipSqrtPI / Math.Sqrt(x)) * ((rc + y * rs) * Math.Sin(x) + (-rc + y * rs) * Math.Cos(x)); } return(value); }
// Carlson's elliptic integral of the second kind // Carlson, Numerische Mathematik, vol 33, 1 (1979) /// <summary> /// Carlson's symmetric form of elliptical integrals /// <para>R<sub>D</sub>(x,y,z) = R<sub>J</sub>(x,y,z,z) = (3/2) * ∫ dt/((t+z)*Sqrt((t+x)(t+y)(t+z))), t={0,∞}</para> /// </summary> /// <param name="x">Argument. Requires x >= 0</param> /// <param name="y">Argument. Requires y >= 0</param> /// <param name="z">Argument. Requires z > 0</param> public static double EllintRD(double x, double y, double z) { if ((!(x >= 0) || double.IsInfinity(x)) || (!(y >= 0) || double.IsInfinity(y)) || (!(z > 0) || double.IsInfinity(z)) || (x + y == 0)) { Policies.ReportDomainError("EllintRD(x: {0}, y: {1}, z: {2}) requires: finite x,y >= 0; finite z > 0; at most one argument == 0", x, y, z); return(double.NaN); } // Special cases from http://dlmf.nist.gov/19.20#iv if (x > y) { Utility.Swap(ref x, ref y); } if (x == 0) { if (y == 0) { return(double.PositiveInfinity); // RD(0, 0, z) } if (y == z) { return(3 * (Math.PI / 4) / (y * Math.Sqrt(y))); // RD(0, y, y) } // // Special handling for common case, from // Numerical Computation of Real or Complex Elliptic Integrals, eq.47 // double xn = Math.Sqrt(y); double yn = Math.Sqrt(z); double x0 = xn; double y0 = yn; double sum = 0; double sum_pow = 0.25; double c = xn - yn; while (Math.Abs(c) >= 2.7 * DoubleLimits.RootMachineEpsilon._2 * xn) { double t = Math.Sqrt(xn * yn); xn = (xn + yn) / 2; yn = t; c = xn - yn; sum_pow *= 2; sum += sum_pow * c * c; } double RF = Math.PI / (xn + yn); // // This following calculation suffers from serious cancellation when y ~ z // unless we combine terms. We have: // // ( ((x0 + y0)/2)^2 - z ) / (z(y-z)) // // Substituting y = x0^2 and z = y0^2 and simplifying we get the following: // double pt = (x0 + 3 * y0) / (4 * z * (x0 + y0)); // // Since we've moved the demoninator from eq.47 inside the expression, we // need to also scale "sum" by the same value: // pt -= sum / (z * (y - z)); return(pt * RF * 3); } if (x == y && x == z) { return(1 / (x * Math.Sqrt(x))); } // Taylor series upper limit const double tolerance = 0.002049052858245406612358440409548690638254492642397575328974; Debug.Assert(Math2.AreNearUlps(Math.Pow(DoubleLimits.MachineEpsilon / 3, 1.0 / 6.0), tolerance, 5), "Incorrect constant"); // duplication double sigma = 0; double factor = 1; int k = 1; for (; k < Policies.MaxSeriesIterations; k++) { double u = (x + y + z + z + z) / 5; double X = (u - x); double Y = (u - y); double Z = (u - z); // Termination condition: double utol = u * tolerance; if (Math.Abs(X) < utol && Math.Abs(Y) < utol && Math.Abs(Z) < utol) { X /= u; Y /= u; Z /= u; // Taylor series expansion to the 5th order double EA = X * Y; double EB = Z * Z; double EC = EA - EB; double ED = EA - 6 * EB; double EE = ED + EC + EC; double S1 = ED * (ED * (9.0 / 88) - Z * EE * (9.0 / 52) - 3.0 / 14); double S2 = Z * (EE / 6 + Z * (-EC * (9.0 / 22) + Z * EA * (3.0 / 26))); double value = 3 * sigma + factor * (1 + S1 + S2) / (u * Math.Sqrt(u)); return(value); } double sx = Math.Sqrt(x); double sy = Math.Sqrt(y); double sz = Math.Sqrt(z); double lambda = sy * (sx + sz) + sz * sx; sigma += factor / (sz * (z + lambda)); factor /= 4; x = (x + lambda) / 4; y = (y + lambda) / 4; z = (z + lambda) / 4; } Policies.ReportDomainError("EllintRD(x: {0}, y: {1}, z: {2}) No convergence after {3} iterations", x, y, z, k); return(double.NaN); }
/// <summary> /// Carlson's symmetric form of elliptical integrals /// <para>R<sub>J</sub>(x,y,z,p) = (3/2) * ∫ dt/((t+p)*Sqrt((t+x)(t+y)(t+z))), t={0,∞}</para> /// </summary> /// <param name="x">Argument. Requires finite x >= 0</param> /// <param name="y">Argument. Requires finite y >= 0</param> /// <param name="z">Argument. Requires finite z >= 0</param> /// <param name="p">Argument. Requires finite p != 0</param> public static double EllintRJ(double x, double y, double z, double p) { if ((!(x >= 0) || double.IsInfinity(x)) || (!(y >= 0) || double.IsInfinity(y)) || (!(z >= 0) || double.IsInfinity(z)) || (p == 0 || double.IsInfinity(p) || double.IsNaN(p)) || (x + y == 0 || y + z == 0 || z + x == 0)) { Policies.ReportDomainError("EllintRJ(x: {0}, y: {1}, z: {2}, p: {3}) requires: finite x,y,z >= 0; finite p != 0; at most one of x,y,z == 0", x, y, z, p); return(double.NaN); } // See Carlson, Numerische Mathematik, vol 33, 1 (1979) double value; // error scales as the 6th power of tolerance const double tolerance = 0.002049052858245406612358440409548690638254492642397575328974; Debug.Assert(Math2.AreNearUlps(Math.Pow(DoubleLimits.MachineEpsilon / 3, 1.0 / 6.0), tolerance, 5), "Incorrect constant"); // reorder x,y,z such that x <= y <= z // so that 0 shows up in x first // and so that the p<0 case below doesn't suffer cancellation errors if (x > y) { Utility.Swap(ref x, ref y); } if (y > z) { Utility.Swap(ref y, ref z); } if (x > y) { Utility.Swap(ref x, ref y); } // for p < 0, the integral is singular, return Cauchy principal value if (p < 0) { // We must ensure that (z - y) * (y - x) is positive. Debug.Assert(x <= y && y <= z, "x, y, z must be in ascending order"); double q = -p; double pmy = (z - y) * (y - x) / (y + q); // p - y Debug.Assert(pmy >= 0); double pAdj = pmy + y; value = pmy * Math2.EllintRJ(x, y, z, pAdj); value -= 3 * Math2.EllintRF(x, y, z); value += 3 * Math.Sqrt((x * y * z) / (x * z + pAdj * q)) * Math2.EllintRC(x * z + pAdj * q, pAdj * q); value /= (y + q); return(value); } if (x == 0) { if (y == 0) //RJ(0, 0, z, p) { return(double.PositiveInfinity); } if (y == z) //RJ(0, y, y, p) { return(3 * (Math.PI / 2) / (y * Math.Sqrt(p) + p * Math.Sqrt(y))); } } if (p == z) { if (z == y) { if (x == y) { return(1 / (x * Math.Sqrt(x))); // RJ(x, x, x, x) } return(EllintRD(x, y, y)); // RJ(x, y, y, y) } return(EllintRD(x, y, z)); // RJ(x, y, z, z) } if (x == y && y == z) { return(EllintRD(p, p, x)); // RJ(x, x, x, p) } // duplication double sigma = 0; double factor = 1; int k = 1; for (; k < Policies.MaxSeriesIterations; k++) { double u = (x + y + z + p + p) / 5; double X = (u - x); double Y = (u - y); double Z = (u - z); double P = (u - p); // Termination condition: double utol = u * tolerance; if (Math.Abs(X) < utol && Math.Abs(Y) < utol && Math.Abs(Z) < utol && Math.Abs(P) < utol) { X /= u; Y /= u; Z /= u; P /= u; // Taylor series expansion to the 5th order double EA = X * Y + Y * Z + Z * X; double EB = X * Y * Z; double EC = P * P; double E2 = EA - 3 * EC; double E3 = EB + 2 * P * (EA - EC); double S1 = 1 + E2 * (E2 * (9.0 / 88) - E3 * (9.0 / 52) - 3.0 / 14); double S2 = EB * (1.0 / 6 + P * (-6.0 / 22 + P * (3.0 / 26))); double S3 = P * ((EA - EC) / 3 - P * EA * (3.0 / 22)); value = 3 * sigma + factor * (S1 + S2 + S3) / (u * Math.Sqrt(u)); return(value); } double sx = Math.Sqrt(x); double sy = Math.Sqrt(y); double sz = Math.Sqrt(z); double lambda = sy * (sx + sz) + sz * sx; double alpha = p * (sx + sy + sz) + sx * sy * sz; alpha *= alpha; double beta = p * (p + lambda) * (p + lambda); sigma += factor * Math2.EllintRC(alpha, beta); factor /= 4; x = (x + lambda) / 4; y = (y + lambda) / 4; z = (z + lambda) / 4; p = (p + lambda) / 4; } Policies.ReportDomainError("EllintRJ(x: {0}, y: {1}, z: {2}, p: {3}) No convergence after {4} iterations", x, y, z, p, k); 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 K{0}(x), or if expScaled == true, e^x * K{0}(x) /// </summary> /// <param name="x"></param> /// <param name="expScaled"></param> /// <returns></returns> public static double K0(double x, bool expScaled = false) { if (!(x >= 0)) { Policies.ReportDomainError("BesselK(v: 0, x: {0}) : Requires x >= 0; complex number result not supported", x); return(double.NaN); } if (x == 0) { return(double.PositiveInfinity); } // Modified Bessel function of the second kind of order zero // minimax rational approximations on intervals, see // Russon and Blair, Chalk River Report AECL-3461, 1969, // as revised by Pavel Holoborodko in "Rational Approximations // for the Modified Bessel Function of the Second Kind - K0(x) // for Computations with Double Precision", see // http://www.advanpix.com/2015/11/25/rational-approximations-for-the-modified-bessel-function-of-the-second-kind-k0-for-computations-with-double-precision/ // // The actual coefficients used are boost's (derived by JM) // since we extend to both greater and lesser precision than the // references above. We can also improve performance WRT to // Holoborodko without loss of precision. double value; if (x <= 1) { // Maximum Deviation Found: 6.077e-17 // Expected Error Term : -6.077e-17 // Maximum Relative Change in Control Points : 7.797e-02 // Max Error found at double precision = Poly : 1.003156e-16 const double Y = 1.137250900268554688; const double p0 = -1.372509002685546267e-01; const double p1 = 2.574916117833312855e-01; const double p2 = 1.395474602146869316e-02; const double p3 = 5.445476986653926759e-04; const double p4 = 7.125159422136622118e-06; const double q0 = 1.000000000000000000e+00; const double q1 = -5.458333438017788530e-02; const double q2 = 1.291052816975251298e-03; const double q3 = -1.367653946978586591e-05; var a = x * x / 4; double P = p0 + a * (p1 + a * (p2 + a * (p3 + a * p4))); double Q = q0 + a * (q1 + a * (q2 + a * q3)); var r1 = (P / Q + Y) * a + 1; // Maximum Deviation Found: 3.429e-18 // Expected Error Term : 3.392e-18 // Maximum Relative Change in Control Points : 2.041e-02 // Max Error found at double precision = Poly : 2.513112e-16 const double c0 = 1.159315156584124484e-01; const double c1 = 2.789828789146031732e-01; const double c2 = 2.524892993216121934e-02; const double c3 = 8.460350907213637784e-04; const double c4 = 1.491471924309617534e-05; const double c5 = 1.627106892422088488e-07; const double c6 = 1.208266102392756055e-09; const double c7 = 6.611686391749704310e-12; a = x * x; var r2 = c0 + a * (c1 + a * (c2 + a * (c3 + a * (c4 + a * (c5 + a * (c6 + a * c7)))))); value = r2 - Math.Log(x) * r1; if (expScaled) { value *= Math.Exp(x); } } else { // Maximum Deviation Found: 4.316e-17 // Expected Error Term : 9.570e-18 // Maximum Relative Change in Control Points : 2.757e-01 // Max Error found at double precision = Poly : 1.001560e-16 const double Y = 1; const double p0 = 2.533141373155002416e-01; const double p1 = 3.628342133984595192e+00; const double p2 = 1.868441889406606057e+01; const double p3 = 4.306243981063412784e+01; const double p4 = 4.424116209627428189e+01; const double p5 = 1.562095339356220468e+01; const double p6 = -1.810138978229410898e+00; const double p7 = -1.414237994269995877e+00; const double p8 = -9.369168119754924625e-02; const double q0 = 1.000000000000000000e+00; const double q1 = 1.494194694879908328e+01; const double q2 = 8.265296455388554217e+01; const double q3 = 2.162779506621866970e+02; const double q4 = 2.845145155184222157e+02; const double q5 = 1.851714491916334995e+02; const double q6 = 5.486540717439723515e+01; const double q7 = 6.118075837628957015e+00; const double q8 = 1.586261269326235053e-01; var a = 1 / x; double P = p0 + a * (p1 + a * (p2 + a * (p3 + a * (p4 + a * (p5 + a * (p6 + a * (p7 + a * p8))))))); double Q = q0 + a * (q1 + a * (q2 + a * (q3 + a * (q4 + a * (q5 + a * (q6 + a * (q7 + a * q8))))))); value = (P / Q + Y); value /= Math.Sqrt(x); if (!expScaled) { // The following is: value * Exp(-x) // when x is large, don't underflow too soon var ex = Math.Exp(-x / 2); value = (value * ex) * ex; } } return(value); }
/// <summary> /// Returns K{n}(x) for integer order /// </summary> /// <param name="n"></param> /// <param name="x"></param> /// <returns></returns> public static double KN(int n, double x) { if (x < 0) { Policies.ReportDomainError("BesselK(v: {0}, x: {1}): Requires x >= 0 for real result", n, x); return(double.NaN); } if (x == 0) { return(double.PositiveInfinity); } // even function // K{-n}(z) = K{n}(z) if (n < 0) { n = -n; } if (n == 0) { return(K0(x)); } if (n == 1) { return(K1(x)); } double v = n; // Hankel is fast, and reasonably accurate, saving us from many recurrences. if (x >= HankelAsym.IKMinX(v)) { return(HankelAsym.K(v, x)); } // 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)) { return(UniformAsym.K(v, x)); } // Since K{v}(x) has a (e^-x)/sqrt(x) multiplier // using recurrence can underflow too quickly for large x, // so, use a scaled version double result; if (x > 1) { double prev = K0(x, true); double current = K1(x, true); // for large v and x this number can get very large // maximum observed K(1000,10) = 2^6211 var(Kv, _, binaryScale) = Recurrence.ForwardK_B(1, x, n - 1, current, prev); // Compute: value * 2^(binaryScale) * e^-x if (x < -DoubleX.MinLogValue) { DoubleX exs = DoubleX.Ldexp(DoubleX.Exp(-x), binaryScale); result = Math2.Ldexp(Kv * exs.Mantissa, exs.Exponent); } else { result = Math.Exp(-x + Math.Log(Kv) + binaryScale * Constants.Ln2); } } else { double prev = K0(x); double current = K1(x); result = Recurrence.ForwardK(1, x, n - 1, current, prev).Kvpn; } return(result); }
/// <summary> /// Carlson's symmetric form of elliptical integrals /// <para>RG = 1/4 ∫ sqrt((t+x)(t+y)(t+z))*(x/(t+x)+y/(t+y)+z/(t+z))*t*dt t={0,∞}</para> /// <para>RG = 1/(4π) ∫∫ sqrt(x*sin^2(θ)*cos^2(φ)+y*sin^2(θ)*sin^2(φ)+z*cos^2(θ))*sin(θ) dθdφ θ={0,π} φ={0,2π}</para> /// </summary> /// <param name="x">Argument. Requires x >= 0</param> /// <param name="y">Argument. Requires y >= 0</param> /// <param name="z">Argument. Requires z >= 0</param> /// <returns></returns> /// <remarks> /// Numerical Computation of Real or Complex Elliptic Integrals, B.C. Carlson, 6 Sep 1994 /// </remarks> /// <see href="http://arxiv.org/abs/math/9409227v1"/> public static double EllintRG(double x, double y, double z) { if (!(x >= 0) && !(y >= 0) && !(z >= 0)) { Policies.ReportDomainError("EllintRG(x: {0}, y: {1}, z: {2}) requires finite x >= 0, y >= 0, z >= 0", x, y, z); return(double.NaN); } // reorder x,y,z such that x <= y <= z if (x > y) { Utility.Swap(ref x, ref y); } if (y > z) { Utility.Swap(ref y, ref z); } if (x > y) { Utility.Swap(ref x, ref y); } if (x == 0) { if (y == 0) { return(Math.Sqrt(z) / 2); // RG(0, 0, z) } if (y == z) { return((Math.PI / 4) * Math.Sqrt(y)); // RG(0, y, y) } double xn = Math.Sqrt(z); double yn = Math.Sqrt(y); double a = (xn + yn) / 2; double sum = 0; double sum_pow = 0.25; double c = xn - yn; while (Math.Abs(c) >= 2.7 * DoubleLimits.RootMachineEpsilon._2 * xn) { double t = Math.Sqrt(xn * yn); xn = (xn + yn) / 2; yn = t; c = xn - yn; sum_pow *= 2; sum += sum_pow * (c * c); } return((a * a - sum) * ((Math.PI / 2) / (xn + yn))); } if (x == y) { if (x == z) { return(Math.Sqrt(x)); // RG(x, x, x) } return((x * EllintRC(z, x) + Math.Sqrt(z)) / 2); //RG(x, x, z) } if (y == z) { return((y * EllintRC(x, y) + Math.Sqrt(x)) / 2); //RG(x, y, y) } // Swap y and z to avoid cancellation error in the equation: // We want the multiplier (x - z)(y - z) < 0 so that the term is additive. // So let x < z and let y > z Utility.Swap(ref y, ref z); return((z * EllintRF(x, y, z) - (x - z) * (y - z) * EllintRD(x, y, z) / 3 + Math.Sqrt(x * y / z)) / 2); }
/// <summary> /// Returns K{1}(x), or if expScaled == true, e^x * K{1}(x) /// </summary> /// <param name="x"></param> /// <param name="expScaled"></param> /// <returns></returns> public static double K1(double x, bool expScaled = false) { if (x < 0) { Policies.ReportDomainError("BesselK(v: 1, x: {0}): Requires x >= 0; complex number result not supported", x); return(double.NaN); } if (x == 0) { return(double.PositiveInfinity); } // Modified Bessel function of the second kind of order zero // minimax rational approximations on intervals, see // Russon and Blair, Chalk River Report AECL-3461, 1969, // as revised by Pavel Holoborodko in "Rational Approximations // for the Modified Bessel Function of the Second Kind - K0(x) // for Computations with Double Precision", see // http://www.advanpix.com/2016/01/05/rational-approximations-for-the-modified-bessel-function-of-the-second-kind-k1-for-computations-with-double-precision/ // // The actual coefficients used are boost's (derived by JM) // since we extend to both greater and lesser precision than the // references above. We can also improve performance WRT to // Holoborodko without loss of precision. double value; if (x <= 1) { double r1, r2; { // Maximum Deviation Found: 1.922e-17 // Expected Error Term : 1.921e-17 // Maximum Relative Change in Control Points : 5.287e-03 // Max Error found at double precision = Poly : 2.004747e-17 const double Y = 8.69547128677368164e-02; const double p0 = -3.62137953440350228e-03; const double p1 = 7.11842087490330300e-03; const double p2 = 1.00302560256614306e-05; const double p3 = 1.77231085381040811e-06; const double q0 = 1.00000000000000000e+00; const double q1 = -4.80414794429043831e-02; const double q2 = 9.85972641934416525e-04; const double q3 = -8.91196859397070326e-06; var a = x * x / 4; double P = p0 + a * (p1 + a * (p2 + a * p3)); double Q = q0 + a * (q1 + a * (q2 + a * q3)); r1 = (P / Q + Y); r1 = (1 + a * (0.5 + r1 * a)) * (0.5 * x); } { // Maximum Deviation Found: 4.053e-17 // Expected Error Term : -4.053e-17 // Maximum Relative Change in Control Points : 3.103e-04 // Max Error found at double precision = Poly : 1.246698e-16 const double p0 = -3.07965757829206184e-01; const double p1 = -7.80929703673074907e-02; const double p2 = -2.70619343754051620e-03; const double p3 = -2.49549522229072008e-05; const double q0 = 1.00000000000000000e+00; const double q1 = -2.36316836412163098e-02; const double q2 = 2.64524577525962719e-04; const double q3 = -1.49749618004162787e-06; var a = x * x; double P = p0 + a * (p1 + a * (p2 + a * p3)); double Q = q0 + a * (q1 + a * (q2 + a * q3)); r2 = P / Q; } value = r2 * x + 1 / x + Math.Log(x) * r1; if (expScaled) { value *= Math.Exp(x); } } else { // Maximum Deviation Found: 8.883e-17 // Expected Error Term : -1.641e-17 // Maximum Relative Change in Control Points : 2.786e-01 // Max Error found at double precision = Poly : 1.258798e-16 const double Y = 1.45034217834472656; const double p0 = -1.97028041029226295e-01; const double p1 = -2.32408961548087617e+00; const double p2 = -7.98269784507699938e+00; const double p3 = -2.39968410774221632e+00; const double p4 = 3.28314043780858713e+01; const double p5 = 5.67713761158496058e+01; const double p6 = 3.30907788466509823e+01; const double p7 = 6.62582288933739787e+00; const double p8 = 3.08851840645286691e-01; const double q0 = 1.00000000000000000e+00; const double q1 = 1.41811409298826118e+01; const double q2 = 7.35979466317556420e+01; const double q3 = 1.77821793937080859e+02; const double q4 = 2.11014501598705982e+02; const double q5 = 1.19425262951064454e+02; const double q6 = 2.88448064302447607e+01; const double q7 = 2.27912927104139732e+00; const double q8 = 2.50358186953478678e-02; var a = 1 / x; double P = p0 + a * (p1 + a * (p2 + a * (p3 + a * (p4 + a * (p5 + a * (p6 + a * (p7 + a * p8))))))); double Q = q0 + a * (q1 + a * (q2 + a * (q3 + a * (q4 + a * (q5 + a * (q6 + a * (q7 + a * q8))))))); value = (P / Q + Y); value /= Math.Sqrt(x); if (!expScaled) { // The following is: value * Exp(-x) // when x is large, don't underflow too soon var ex = Math.Exp(-x / 2); value = (value * ex) * ex; } } return(value); }
/// <summary> /// Returns the natural log of the Gamma Function = ln(|Γ(x)|). /// Sets the sign = Sign(Γ(x)); 0 for poles or indeterminate. /// </summary> /// <param name="x">Lgamma function argument</param> /// <param name="sign">Lgamma output value = Sign(Γ(x))</param> public static double Lgamma(double x, out int sign) { sign = 0; if (double.IsNaN(x)) { Policies.ReportDomainError("Lgamma(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Lgamma(x: {0}): Requires x != -Infinity", x); return(double.PositiveInfinity); } sign = 1; return(double.PositiveInfinity); } double result = 0; sign = 1; if (x <= 0) { if (IsInteger(x)) { sign = 0; Policies.ReportPoleError("Lgamma(x: {0}): Evaluation at zero, or a negative integer", x); return(double.PositiveInfinity); } if (x > -GammaSmall.UpperLimit) { return(GammaSmall.Lgamma(x, out sign)); } if (x <= -StirlingGamma.LowerLimit) { // Our Stirling routine does the reflection // So no need to reflect here result = StirlingGamma.Lgamma(x, out sign); return(result); } else { double product = 1; double xm2 = x - 2; double xm1 = x - 1; while (x < 1) { product *= x; xm2 = xm1; xm1 = x; x += 1; } if (product < 0) { sign = -1; product = -product; } result = -Math.Log(product) + _Lgamma.Rational_1_3(x, xm1, xm2); return(result); } } else if (x < 1) { if (x < GammaSmall.UpperLimit) { return(GammaSmall.Lgamma(x, out sign)); } // Log(Γ(x)) = Log(Γ(x+1)/x) result = -Math.Log(x) + _Lgamma.Rational_1_3(x + 1, x, x - 1); } else if (x < 3) { result = _Lgamma.Rational_1_3(x, x - 1, x - 2); } else if (x < 8) { // use recurrence to shift x to [2, 3) double product = 1; while (x >= 3) { product *= --x; } result = Math.Log(product) + _Lgamma.Rational_1_3(x, x - 1, x - 2); } else { // regular evaluation: result = StirlingGamma.Lgamma(x); } return(result); }
/// <summary> /// Returns the Gamma Function = Γ(x) /// </summary> /// <param name="x">Tgamma function argument</param> public static double Tgamma(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("Tgamma(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Tgamma(x: {0}): Requires x is not negative infinity", x); return(double.NaN); } return(double.PositiveInfinity); } double result = 1; if (x <= 0) { if (IsInteger(x)) { Policies.ReportPoleError("Tgamma(x: {0}): Requires x != 0 and x be a non-negative integer", x); return(double.NaN); } if (x >= -GammaSmall.UpperLimit) { return(GammaSmall.Tgamma(x)); } if (x <= -StirlingGamma.LowerLimit) { const double MinX = -185; if (x < MinX) { return(0); } // If we use the reflection formula directly for x < -NegMaxGamma, Tgamma will overflow; // so, use recurrence to get x > -NegMaxGamma. // The result is likely to be subnormal or zero. double shiftedX = x; double recurrenceMult = 1.0; double NegMaxGamma = -(Math2.MaxFactorialIndex + 1); while (shiftedX < NegMaxGamma) { recurrenceMult *= shiftedX; shiftedX += 1; } result = -(Math.PI / StirlingGamma.Tgamma(-shiftedX)) / (shiftedX * recurrenceMult * Math2.SinPI(shiftedX)); return(result); } // shift x to > 0: double product = 1; while (x < 0) { product *= x++; } result /= product; // fall through } if (x < 1) { if (x <= GammaSmall.UpperLimit) { result *= GammaSmall.Tgamma(x); } else { // Γ(x) = Exp(LogΓ(x+1))/x result *= Math.Exp(_Lgamma.Rational_1_3(x + 1, x, x - 1)) / x; } } else if (IsInteger(x) && (x <= Math2.FactorialTable.Length)) { // Γ(n) = (n-1)! result *= Math2.FactorialTable[(int)x - 1]; } else if (x < StirlingGamma.LowerLimit) { // ensure x is in [1, 3) // if x >= 3, use recurrence to shift x to [2, 3) while (x >= 3) { result *= --x; } result *= Math.Exp(_Lgamma.Rational_1_3(x, x - 1, x - 2)); } else { result *= StirlingGamma.Tgamma(x); } return(result); }
/// <summary> /// Returns the ratio of gamma functions /// <para>TgammaDeltaRatio(x,delta) = Γ(x)/Γ(x+delta)</para> /// </summary> /// <param name="x">The argument for the gamma function in the numerator. Requires x > 0</param> /// <param name="delta">The offset for the denominator. Requires x + delta > 0</param> public static double TgammaDeltaRatio(double x, double delta) { double xpd = x + delta; if (double.IsNaN(x) || double.IsNaN(delta)) { Policies.ReportDomainError("TgammaDeltaRatio(x: {0}, delta: {1}): Requires x > 0; x + delta > 0", x, delta); return double.NaN; } // Γ(x)/Γ(x) == 1 // this needs to be here in case x = 0 if (delta == 0) return 1; if (!(x > 0)) { Policies.ReportDomainError("TgammaDeltaRatio(x: {0}, delta: {1}): Requires x > 0; negative x not implemented", x, delta); return double.NaN; } if (!(xpd > 0)) { Policies.ReportDomainError("TgammaDeltaRatio(x: {0}, delta: {1}): Requires x+delta > 0", x, delta); return double.NaN; } if (double.IsInfinity(x)) { if (double.IsInfinity(delta)) { Policies.ReportDomainError("TgammaDeltaRatio(x: {0}, delta: {1}): Infinity/Infinity", x, delta); return double.NaN; } return double.PositiveInfinity; } if (double.IsInfinity(delta)) return 0.0; if (IsInteger(delta)) { if (IsInteger(x)) { // Both x and delta are integers, see if we can just use table lookup // of the factorials to get the result: if ((x <= Math2.FactorialTable.Length) && (x + delta <= Math2.FactorialTable.Length)) return Math2.FactorialTable[(int)x - 1] / Math2.FactorialTable[(int)(x + delta) - 1]; } // if delta is a small integer, we can use a finite product if (Math.Abs(delta) < 20) { if (delta == 0) return 1; if (delta < 0) { x -= 1; double result = x; while ( ++delta < 0 ) { x -= 1; result *= x; } return result; } else { double result = x; while ( --delta > 0 ) { x += 1; result *= x; } return 1/result; } } } // our Tgamma ratio already handles cases where // one or both arguments are small const double LowerLimit = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; // 1.73eps if (x < LowerLimit || xpd < LowerLimit) return Math2.TgammaRatio(x, xpd); if ((x < 1 && delta > Math2.FactorialTable.Length) || (xpd < 1 && x > Math2.FactorialTable.Length)) return Math2.TgammaRatio(x, xpd); // Use the Lanczos approximation to compute the delta ratio return Lanczos.TgammaDeltaRatio(x, delta); }
/// <summary> /// Returns the Beta function /// <para>B(a,b) = Γ(a)*Γ(b)/Γ(a+b)</para> /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="b">Requires finite b > 0</param> public static double Beta(double a, double b) { double c = a + b; if ((!(a > 0) || double.IsInfinity(a)) || (!(b > 0) || double.IsInfinity(b))) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite a,b > 0", a, b); return double.NaN; } if (double.IsInfinity(c)) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite c == a+b: c = {2}", a, b, c); return double.NaN; } // Special cases: if ((c == a) && (b < DoubleLimits.MachineEpsilon)) return Math2.Tgamma(b); if ((c == b) && (a < DoubleLimits.MachineEpsilon)) return Math2.Tgamma(a); if (b == 1) return 1 / a; if (a == 1) return 1 / b; // B(a,b) == B(b, a) if (a < b) Utility.Swap(ref a, ref b); // from this point a >= b if (a < 1) { // When x < TinyX, Γ(x) ~ 1/x const double SmallX = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if ( a < SmallX ) { if (c < SmallX) return (c / a) / b; Debug.Assert(c <= GammaSmall.UpperLimit); return 1/(GammaSmall.Tgamma(c) * a * b); } if ( b < SmallX ) return Tgamma(a) / (b * Tgamma(c)); return Tgamma(a) * ( Tgamma(b)/Tgamma(c) ); } // Our Stirling series is more accurate than Lanczos if (a >= StirlingGamma.LowerLimit ) { if (b >= StirlingGamma.LowerLimit) return StirlingGamma.Beta(a, b); // for large a, Γ(a)/Γ(a + b) ~ a^-b, so don't underflow too soon if (b < 1 || b * Math.Log(a) <= -DoubleLimits.MinLogValue) return Tgamma(b) * StirlingGamma.TgammaDeltaRatio(a, b); // fall through for very large a small 1 < b < LowerLimit } return Lanczos.Beta(a, b); }
/// <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); }
/// <summary> /// Returns Log(Beta(a,b)) /// </summary> /// <param name="a">Requires finite a > 0</param> /// <param name="b">Requires finite b > 0</param> public static double LogBeta(double a, double b) { double c = a + b; if ((!(a > 0) || double.IsInfinity(a)) || (!(b > 0) || double.IsInfinity(b))) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite a,b > 0", a, b); return double.NaN; } if (double.IsInfinity(c)) { Policies.ReportDomainError("Beta(a: {0}, b: {1}): Requires finite c == a+b: c = {2}", a, b, c); return double.NaN; } // Special cases: if ((c == a) && (b < DoubleLimits.MachineEpsilon)) return Math2.Lgamma(b); if ((c == b) && (a < DoubleLimits.MachineEpsilon)) return Math2.Lgamma(a); if (b == 1) return -Math.Log(a); if (a == 1) return -Math.Log(b); // B(a,b) == B(b, a) if (a < b) Utility.Swap(ref a, ref b); // from this point a >= b if (a < 1) { // When x < TinyX, Γ(x) ~ 1/x const double SmallX = DoubleLimits.MachineEpsilon / Constants.EulerMascheroni; if (a < SmallX) { if (c < SmallX) { // In this range, Beta(a, b) ~= 1/a + 1/b // so, we won't have an argument overflow as long as // the min(a, b) >= 2*DoubleLimits.MinNormalValue if (b < 2 * DoubleLimits.MinNormalValue) return Math.Log(c / a) - Math.Log(b); return Math.Log((c / a) / b); } Debug.Assert(c <= GammaSmall.UpperLimit); return -Math.Log((GammaSmall.Tgamma(c) * a * b)); } if (b < SmallX) return Math.Log(Tgamma(a) / (b * Tgamma(c))); return Math.Log((Tgamma(a) / Tgamma(c)) * Tgamma(b)); } else if (a >= StirlingGamma.LowerLimit) { if ( b >= StirlingGamma.LowerLimit ) return StirlingGamma.LogBeta(a, b); return StirlingGamma.LgammaDelta(a, b) + Lgamma(b); } return Lanczos.LogBeta(a, b); }
/// <summary> /// Returns the Jacobi elliptic functions: sn, cn, dn /// </summary> /// <param name="k">The modulus</param> /// <param name="u">The argument</param> /// <returns></returns> static JacobiSnCnDn JacobiElliptic(double k, double u) { if (double.IsNaN(k) || double.IsInfinity(k) || double.IsNaN(u) || double.IsInfinity(u)) { Policies.ReportDomainError("Jacobi(k: {0}, u: {1}): Requires finite u, k", k, u); return(new JacobiSnCnDn { Sn = double.NaN, Cn = double.NaN, Dn = double.NaN }); } // For k < 0 or k > 1, see: http://dlmf.nist.gov/22.17#i if (k < 0) { k = -k; } if (k > 1) { var results = JacobiElliptic(1 / k, u * k); return(new JacobiSnCnDn { Sn = results.Sn / k, Cn = results.Dn, Dn = results.Cn }); } if (u == 0) { return new JacobiSnCnDn { Sn = 0, Cn = 1, Dn = 1 } } ; if (k == 0) { return new JacobiSnCnDn { Sn = Math.Sin(u), Cn = Math.Cos(u), Dn = 1 } } ; if (k == 1) { double sech = 1 / Math.Cosh(u); return(new JacobiSnCnDn { Sn = Math.Tanh(u), Cn = sech, Dn = sech }); } double sn, cn, dn; if (k < DoubleLimits.RootMachineEpsilon._4) { // Asymptotic form from A&S 16.13: // and http://dlmf.nist.gov/22.10#ii double tu = Math.Tanh(u); double su = Math.Sin(u); double cu = Math.Cos(u); double m = k * k; sn = su - m * (u - cu * su) * cu / 4; cn = cu + m * (u - cu * su) * su / 4; dn = 1 - m * su * su / 2; } else if (k * k > 1 - DoubleLimits.RootMachineEpsilon._2) { // Asymptotic form from A&S 16.15: // and http://dlmf.nist.gov/22.10#ii // O(k'^4) = O((1-k^2)^2) double tu = Math.Tanh(u); double cu = Math.Cosh(u); double su = Math.Sinh(u); double sech = 1 / Math.Cosh(u); double kc = 1 - k; double kp2 = kc * (2 - kc); // k'^2 = 1-k^2 dn = (sech + kp2 * (u * sech + su) * tu) / 4; cn = (sech + kp2 * (u * sech - su) * tu) / 4; sn = (tu + kp2 * (tu - u / (cu * cu))) / 4; //sn -= (72 * u * cu + 4 * (8 * u * u - 5) * su - 19 * Math.Sinh(3 * u) + Math.Sinh(5 * u)) * sec * sec * sec * kp2 * kp2 / 512; } else { double kc = 1 - k; double k_prime = k < 0.5 ? Math.Sqrt((1 - k) * (1 + k)) : Math.Sqrt(kc * (2 - kc)); double T1; double T0 = Jacobi_Recurse(k, u, 1, k_prime, 0, out T1); sn = Math.Sin(T0); cn = Math.Cos(T0); dn = Math.Cos(T0) / Math.Cos(T1 - T0); } return(new JacobiSnCnDn { Sn = sn, Cn = cn, Dn = dn }); }
/// <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); }
public static double Imp(double k, double n, double nc, double phi) { // Note arg nc = 1-n, possibly without cancellation errors Debug.Assert(k >= -1 && k <= 1, "Requires |k| <= 1: k = " + k); // See: http://dlmf.nist.gov/19.6#iv if (phi == 0) { return(0); } if (phi == Math.PI / 2) { return(Math2.EllintPi(k, n)); } if (n == 0) { if (k == 0) { return(phi); } return(Math2.EllintF(k, phi)); } // Carlson's algorithm works only for |phi| <= π/2, // use the integrand's periodicity to normalize phi // Π(k, n, phi + π*mult) = Π(k, n, phi) + 2*mult*Π(k,n) double result = 0; double rphi = Math.Abs(phi); if (rphi > Math.PI / 2) { // Normalize periodicity so that |rphi| <= π/2 var(angleMultiple, angleRemainder) = Trig.RangeReducePI(rphi); double mult = 2 * angleMultiple; rphi = angleRemainder; if ((mult > 0) && (nc > 0)) { result = mult * _EllintPi.Imp(k, n, nc); } } if (k == 0) { double ncr; // A&S 17.7.20: if (n < 1) { if (nc == 1) { return(phi); } ncr = Math.Sqrt(nc); result += Math.Atan(ncr * Math.Tan(rphi)) / ncr; } else if (n == 1) { result += Math.Tan(rphi); } else { // n > 1: ncr = Math.Sqrt(-nc); result += Math2.Atanh(ncr * Math.Tan(rphi)) / ncr; } return((phi < 0) ? -result : result); } double sphi = Math.Sin(Math.Abs(phi)); if (n > 1 / (sphi * sphi)) { // Complex result is a domain error: Policies.ReportDomainError("EllintPi(k: {0}, nu: {1}, phi: {2}): Complex results not supported. Requires n > 1 / sin^2(phi)", k, n, phi); return(double.NaN); } // Special cases first: if (n < 0) { // // If we don't shift to 0 <= n <= 1 we get // cancellation errors later on. Use // A&S 17.7.15/16 to shift to n > 0: // double k2 = k * k; double N = (k2 - n) / nc; double Nc = (1 - k2) / nc; // Nc = 1-N = (1-k^2)/nc // check to see if a rounding error occurred // k^2 <= N <= 1 if (N < k2) { #if EXTRA_DEBUG Debug.WriteLine("Rounding error: EllintPi(k: {0} , nu: {1} , phi: {2})", k, n, phi); #endif N = k2; Nc = 1 - N; } double p2 = Math.Sqrt(-n * N); double delta = Math.Sqrt(1 - k2 * sphi * sphi); // Reduce A&S 17.7.15/16 // Mathematica eqns below // N is protected in Mathematica, so use V // V = (k2 - n)/(1 - n) // Assuming[(k2 >= 0 && k2 <= 1) && n < 0, FullSimplify[Sqrt[(1 - V)*(1 - k2/V)]/Sqrt[((1 - n)*(1 - k2/n))]]] // Result: ((-1 + k2) n)/((-1 + n) (-k2 + n)) // Assuming[(k2 >= 0 && k2 <= 1) && n < 0, FullSimplify[k2/(Sqrt[-n*(k2 - n)/(1 - n)]*Sqrt[(1 - n)*(1 - k2/n)])]] // Result: k2/(k2 - n) // Assuming[(k2 >= 0 && k2 <= 1) && n < 0, FullSimplify[Sqrt[1/((1 - n)*(1 - k2/n))]]] // Result: Sqrt[n/((k2 - n) (-1 + n))] double nResult = -(n / nc) * ((1 - k2) / (k2 - n)) * _EllintPi.Imp(k, N, Nc, rphi); nResult += k2 / (k2 - n) * Math2.EllintF(k, rphi); nResult += Math.Sqrt((n / nc) / (n - k2)) * Math.Atan((p2 / (2 * delta)) * Math.Sin(2 * rphi)); result += nResult; return((phi < 0) ? -result : result); } double sinp = Math.Sin(rphi); double cosp = Math.Cos(rphi); double x = cosp * cosp; double t = sinp * sinp; double y = 1 - k * k * t; double z = 1; double p = (n * t < 0.5) ? 1 - n * t : x + nc * t; result += sinp * (Math2.EllintRF(x, y, z) + n * t * Math2.EllintRJ(x, y, z, p) / 3); return((phi < 0) ? -result : result); }
/// <summary> /// Returns Y{1}(x) /// </summary> /// <param name="x"></param> /// <returns></returns> public static double Y1(double x) { if (x <= 0) { Policies.ReportDomainError("BesselY(v: 1, x: {0}): Requires x >= 0; complex number result not supported", x); return(double.NaN); } if (double.IsInfinity(x)) { return(0); } // Bessel function of the second kind of order one // x <= 8, minimax rational approximations on root-bracketing intervals // x > 8, Hankel asymptotic expansion in Hart, Computer Approximations, 1968 double value, factor; if (x < DoubleLimits.MachineEpsilon) { return((x / Math.PI) * Math.Log(x / 2) - 2 / (Math.PI * x) - (x / (2 * Math.PI)) * (1 - 2 * Constants.EulerMascheroni)); } if (x <= 4) // x in (0, 4] { const double Zero1 = 2.1971413260310170351e+00; const double Zero1a = 2359162535.0 / 1073741824; const double Zero1b = -1.56191001087854667603372694698166849966580723533404956e-12; const double p0 = 4.0535726612579544093e+13; const double p1 = 5.4708611716525426053e+12; const double p2 = -3.7595974497819597599e+11; const double p3 = 7.2144548214502560419e+09; const double p4 = -5.9157479997408395984e+07; const double p5 = 2.2157953222280260820e+05; const double p6 = -3.1714424660046133456e+02; const double q0 = 3.0737873921079286084e+14; const double q1 = 4.1272286200406461981e+12; const double q2 = 2.7800352738690585613e+10; const double q3 = 1.2250435122182963220e+08; const double q4 = 3.8136470753052572164e+05; const double q5 = 8.2079908168393867438e+02; const double q6 = 1.0; double z = x * x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * p6))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * q6))))); double y = (2 / Math.PI) * Math.Log(x / Zero1) * _Bessel.J1(x); factor = (x + Zero1) * ((x - Zero1a) - Zero1b) / x; value = y + factor * (P / Q); } else if (x <= 8) // x in (4, 8] { const double Zero2 = 5.4296810407941351328e+00; const double Zero2a = 11660151249.0 / 2147483648; const double Zero2b = -1.81486215767496919599158034162425239708541634963014629e-11; const double p0 = 1.1514276357909013326e+19; const double p1 = -5.6808094574724204577e+18; const double p2 = -2.3638408497043134724e+16; const double p3 = 4.0686275289804744814e+15; const double p4 = -5.9530713129741981618e+13; const double p5 = 3.7453673962438488783e+11; const double p6 = -1.1957961912070617006e+09; const double p7 = 1.9153806858264202986e+06; const double p8 = -1.2337180442012953128e+03; const double q0 = 5.3321844313316185697e+20; const double q1 = 5.6968198822857178911e+18; const double q2 = 3.0837179548112881950e+16; const double q3 = 1.1187010065856971027e+14; const double q4 = 3.0221766852960403645e+11; const double q5 = 6.3550318087088919566e+08; const double q6 = 1.0453748201934079734e+06; const double q7 = 1.2855164849321609336e+03; const double q8 = 1.0; double z = x * x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * (p6 + z * (p7 + z * p8))))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * (q7 + z * q8))))))); double y = (2 / Math.PI) * Math.Log(x / Zero2) * _Bessel.J1(x); factor = (x + Zero2) * ((x - Zero2a) - Zero2b) / x; value = y + factor * (P / Q); } else // x in (8, infinity) { double y = 8 / x; double z = y * y; double rc, rs; { const double p0 = -4.4357578167941278571e+06; const double p1 = -9.9422465050776411957e+06; const double p2 = -6.6033732483649391093e+06; const double p3 = -1.5235293511811373833e+06; const double p4 = -1.0982405543459346727e+05; const double p5 = -1.6116166443246101165e+03; const double p6 = 0.0; const double q0 = -4.4357578167941278568e+06; const double q1 = -9.9341243899345856590e+06; const double q2 = -6.5853394797230870728e+06; const double q3 = -1.5118095066341608816e+06; const double q4 = -1.0726385991103820119e+05; const double q5 = -1.4550094401904961825e+03; const double q6 = 1.0; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * p6))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * q6))))); rc = P / Q; } { const double p0 = 3.3220913409857223519e+04; const double p1 = 8.5145160675335701966e+04; const double p2 = 6.6178836581270835179e+04; const double p3 = 1.8494262873223866797e+04; const double p4 = 1.7063754290207680021e+03; const double p5 = 3.5265133846636032186e+01; const double p6 = 0.0; const double q0 = 7.0871281941028743574e+05; const double q1 = 1.8194580422439972989e+06; const double q2 = 1.4194606696037208929e+06; const double q3 = 4.0029443582266975117e+05; const double q4 = 3.7890229745772202641e+04; const double q5 = 8.6383677696049909675e+02; const double q6 = 1.0; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * p6))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * q6))))); rs = P / Q; } // The following code is: // double z = x - 0.75f * Math.PI; // value = (Constants.RecipSqrtHalfPI / Math.Sqrt(x)) * (rc * Math.Sin(z) + y * rs * Math.Cos(z)); // Which can be written as: //(double, double) sincos = Math2.SinCos(x,-0.75); //value = (Constants.RecipSqrtHalfPI / Math.Sqrt(x)) * (rc * sincos.Item1 + y * rs * sincos.Item2); // Or, using trig addition rules, simplified to: value = (Constants.RecipSqrtPI / Math.Sqrt(x)) * ((-rc + y * rs) * Math.Sin(x) - (rc + y * rs) * Math.Cos(x)); } return(value); }
/// <summary> /// Returns the value of the Exponential Integral /// <para>Ei(x) = ∫ e^t/t dt, t={-∞,x}</para> /// </summary> public static double Expint(double x) { if (x < 0) { return(-E1(-x)); } if (double.IsNaN(x)) { Policies.ReportDomainError("Expint(x: {0}) NaN not allowed", x); return(double.NaN); } if (x == 0) { return(double.NegativeInfinity); } double result; if (x <= 6) { // Maximum Deviation Found: 2.852e-18 // Expected Error Term: 2.852e-18 // Max Error found at double precision = Poly: 2.636335e-16 Cheb: 4.187027e-16 const double R = 0.372507410781366634461991866580119133535689497771654051555657435242200120636201854384926049951548942392; const double R1 = 1677624236387711.0 / 4503599627370496.0; const double R2 = 0.131401834143860282009280387409357165515556574352422001206362e-16; const double p0 = 2.98677224343598593013; const double p1 = 0.356343618769377415068; const double p2 = 0.780836076283730801839; const double p3 = 0.114670926327032002811; const double p4 = 0.0499434773576515260534; const double p5 = 0.00726224593341228159561; const double p6 = 0.00115478237227804306827; const double p7 = 0.000116419523609765200999; const double p8 = 0.798296365679269702435e-5; const double p9 = 0.2777056254402008721e-6; const double q0 = 1; const double q1 = -1.17090412365413911947; const double q2 = 0.62215109846016746276; const double q3 = -0.195114782069495403315; const double q4 = 0.0391523431392967238166; const double q5 = -0.00504800158663705747345; const double q6 = 0.000389034007436065401822; const double q7 = -0.138972589601781706598e-4; double z = (x / 3) - 1; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * (p6 + z * (p7 + z * (p8 + z * p9)))))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * q7)))))); double xmr = (x - R1) - R2; result = (P / Q) * xmr; result += (Math.Abs(xmr) < 0.1) ? Math2.Log1p(xmr / R) : Math.Log(x / R); } else if (x <= 10) { // Maximum Deviation Found: 6.546e-17 // Expected Error Term: 6.546e-17 // Max Error found at double precision = Poly: 6.890169e-17 Cheb: 6.772128e-17 const double Y = 1.158985137939453125; const double p0 = 0.00139324086199402804173; const double p1 = -0.0349921221823888744966; const double p2 = -0.0264095520754134848538; const double p3 = -0.00761224003005476438412; const double p4 = -0.00247496209592143627977; const double p5 = -0.000374885917942100256775; const double p6 = -0.554086272024881826253e-4; const double p7 = -0.396487648924804510056e-5; const double q0 = 1; const double q1 = 0.744625566823272107711; const double q2 = 0.329061095011767059236; const double q3 = 0.100128624977313872323; const double q4 = 0.0223851099128506347278; const double q5 = 0.00365334190742316650106; const double q6 = 0.000402453408512476836472; const double q7 = 0.263649630720255691787e-4; double z = x / 2 - 4; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * (p6 + z * p7)))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * q7)))))); result = Math.Exp(x) / x * (Y + P / Q) + x; } else if (x <= 20) { // Maximum Deviation Found: 1.843e-17 // Expected Error Term: -1.842e-17 // Max Error found at double precision = Poly: 4.375868e-17 Cheb: 5.860967e-17 const double Y = 1.0869731903076171875; const double p0 = -0.00893891094356945667451; const double p1 = -0.0484607730127134045806; const double p2 = -0.0652810444222236895772; const double p3 = -0.0478447572647309671455; const double p4 = -0.0226059218923777094596; const double p5 = -0.00720603636917482065907; const double p6 = -0.00155941947035972031334; const double p7 = -0.000209750022660200888349; const double p8 = -0.138652200349182596186e-4; const double q0 = 1; const double q1 = 1.97017214039061194971; const double q2 = 1.86232465043073157508; const double q3 = 1.09601437090337519977; const double q4 = 0.438873285773088870812; const double q5 = 0.122537731979686102756; const double q6 = 0.0233458478275769288159; const double q7 = 0.00278170769163303669021; const double q8 = 0.000159150281166108755531; double z = x / 5 - 3; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * (p6 + z * (p7 + z * p8))))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * (q7 + z * q8))))))); result = Math.Exp(x) / x * (Y + P / Q) + x; } else if (x <= 40) { // Maximum Deviation Found: 5.102e-18 // Expected Error Term: 5.101e-18 // Max Error found at double precision = Poly: 1.441088e-16 Cheb: 1.864792e-16 const double Y = 1.03937530517578125; const double p0 = -0.00356165148914447597995; const double p1 = -0.0229930320357982333406; const double p2 = -0.0449814350482277917716; const double p3 = -0.0453759383048193402336; const double p4 = -0.0272050837209380717069; const double p5 = -0.00994403059883350813295; const double p6 = -0.00207592267812291726961; const double p7 = -0.000192178045857733706044; const double p8 = -0.113161784705911400295e-9; const double q0 = 1; const double q1 = 2.84354408840148561131; const double q2 = 3.6599610090072393012; const double q3 = 2.75088464344293083595; const double q4 = 1.2985244073998398643; const double q5 = 0.383213198510794507409; const double q6 = 0.0651165455496281337831; const double q7 = 0.00488071077519227853585; double z = x / 10 - 3; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * (p6 + z * (p7 + z * p8))))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * (q6 + z * q7)))))); result = Math.Exp(x) / x * (Y + P / Q) + x; } else { // Max Error found at double precision = 3.381886e-17 const double Y = 1.013065338134765625; const double p0 = -0.0130653381347656243849; const double p1 = 0.19029710559486576682; const double p2 = 94.7365094537197236011; const double p3 = -2516.35323679844256203; const double p4 = 18932.0850014925993025; const double p5 = -38703.1431362056714134; const double q0 = 1; const double q1 = 61.9733592849439884145; const double q2 = -2354.56211323420194283; const double q3 = 22329.1459489893079041; const double q4 = -70126.245140396567133; const double q5 = 54738.2833147775537106; const double q6 = 8297.16296356518409347; double z = 1 / x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * q6))))); result = Y + P / Q; if (x < 41) { result *= Math.Exp(x) / x; } else { // Avoid premature overflow if we can: const double exp40 = 2.35385266837019985407899910749034804508871617254555467236651e17; result *= (Math.Exp(x - 40) / x); result *= exp40; } result += x; } return(result); }
/// <summary> /// Computes Y{v}(x), where v is an integer /// </summary> /// <param name="v">Integer order</param> /// <param name="x">Argument. Requires x > 0</param> /// <returns></returns> public static DoubleX YN(double v, double x) { if ((x == 0) && (v == 0)) { return(double.NegativeInfinity); } if (x <= 0) { Policies.ReportDomainError("BesselY(v: {0}, x: {1}): Complex number result not supported. Requires x >= 0", v, x); return(double.NaN); } // // Reflection comes first: // double sign = 1; if (v < 0) { // Y_{-n}(z) = (-1)^n Y_n(z) if (Math2.IsOdd(v)) { sign = -1; } v = -v; } Debug.Assert(v >= 0 && x >= 0); if (v > int.MaxValue) { Policies.ReportNotImplementedError("BesselY(v: {0}, x: {1}): Large integer values not yet implemented", v, x); return(double.NaN); } int n = (int)v; if (n == 0) { return(Y0(x)); } if (n == 1) { return(sign * Y1(x)); } if (x < DoubleLimits.RootMachineEpsilon._2) { DoubleX smallArgValue = YN_SmallArg(n, x); return(smallArgValue * sign); } // 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, false, true)) { return(sign * Yv); } } // forward recurrence always OK (though unstable) double prev = Y0(x); double current = Y1(x); var(Yvpn, Yvpnm1, YScale) = Recurrence.ForwardJY_B(1.0, x, n - 1, current, prev); return(DoubleX.Ldexp(sign * Yvpn, YScale)); }
/// <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> /// Returns the Trigamma function /// <para>ψ'(x) = d/dx(ψ(x)) = d''/dx(ln(Γ(x)))</para> /// </summary> /// <param name="x">Trigamma function argument</param> public static double Trigamma(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("Trigamma(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { Policies.ReportDomainError("Trigamma(x: {0}): Requires x != -Infinity", x); return(double.NaN); } return(0); } double result = 0; // // Check for negative arguments and use reflection: // if (x <= 0) { if (IsInteger(x)) { Policies.ReportPoleError("Trigamma(x: {0}) Requires x is not an integer when x <= 0", x); return(double.NaN); } // Reflect: double z = 1 - x; double s = Math.Abs(x) < Math.Abs(z) ? Math2.SinPI(x) : Math2.SinPI(z); return(-Trigamma(z) + (Math.PI * Math.PI) / (s * s)); } if (x < 1) { result = 1 / (x * x); x += 1; } if (x <= 2) { // Max error in interpolated form: 3.736e-017 const double Y_1_2 = 2.1093254089355469; const double p0 = -1.1093280605946045; const double p1 = -3.8310674472619321; const double p2 = -3.3703848401898283; const double p3 = 0.28080574467981213; const double p4 = 1.6638069578676164; const double p5 = 0.64468386819102836; const double q0 = 1.0; const double q1 = 3.4535389668541151; const double q2 = 4.5208926987851437; const double q3 = 2.7012734178351534; const double q4 = 0.64468798399785611; const double q5 = -0.20314516859987728e-6; double z = x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * q5)))); result += (Y_1_2 + P / Q) / (x * x); } else if (x <= 4) { // Max error in interpolated form: 1.159e-017 const double p0 = -0.13803835004508849e-7; const double p1 = 0.50000049158540261; const double p2 = 1.6077979838469348; const double p3 = 2.5645435828098254; const double p4 = 2.0534873203680393; const double p5 = 0.74566981111565923; const double q0 = 1.0; const double q1 = 2.8822787662376169; const double q2 = 4.1681660554090917; const double q3 = 2.7853527819234466; const double q4 = 0.74967671848044792; const double q5 = -0.00057069112416246805; double z = 1 / x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * p5)))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * q5)))); result += (1 + P / Q) / x; } else { // Maximum Deviation Found: 6.896e-018 // Expected Error Term : -6.895e-018 // Maximum Relative Change in Control Points : 8.497e-004 const double p0 = 0.68947581948701249e-17; const double p1 = 0.49999999999998975; const double p2 = 1.0177274392923795; const double p3 = 2.498208511343429; const double p4 = 2.1921221359427595; const double p5 = 1.5897035272532764; const double p6 = 0.40154388356961734; const double q0 = 1.0; const double q1 = 1.7021215452463932; const double q2 = 4.4290431747556469; const double q3 = 2.9745631894384922; const double q4 = 2.3013614809773616; const double q5 = 0.28360399799075752; const double q6 = 0.022892987908906897; double z = 1 / x; double P = p0 + z * (p1 + z * (p2 + z * (p3 + z * (p4 + z * (p5 + z * p6))))); double Q = q0 + z * (q1 + z * (q2 + z * (q3 + z * (q4 + z * (q5 + z * q6))))); result += (1 + P / Q) / x; } return(result); }
/// <summary> /// Returns the falling factorial: /// <para>(x)_{_n} = x*(x-1)*(x-2)...*(x-(n-1)) = Γ(x+1)/Γ(x-n+1)</para> /// </summary> /// <param name="x"></param> /// <param name="n">Requires n ≥ 0</param> /// <returns> /// <para>if n > 0, x*(x-1)*(x-2)...*(x-(n-1))</para> /// <para>if n == 0, 1</para> /// </returns> public static double FactorialFalling(double x, int n) { // This section uses the following notation: // falling factorial: (x)_{_n} // rising factorial: (x)_{n} // Standard eqn for the falling factorial: // (x)_{_n} = Γ(x+1)/Γ(x+1-n) double result = double.NaN; // (x)_{_0} = 1 if (n == 0) { return(1); } if ((double.IsNaN(x)) || (n < 0)) { Policies.ReportDomainError("FactorialFalling(x: {0}, n: {1}): Requires finite x; n >= 0", x, n); return(double.NaN); } if (n == 1) { return(x); } if (x == 0) { return(0); } if (x < 0) { // // For x < 0 we really have a rising factorial // modulo a possible change of sign: // (x)_{_n} = (-1)^n * (-x)_{n} result = FactorialRising(-x, n); return(IsOdd(n) ? -result : result); } // standard case: Γ(x+1)/Γ(x+1-n) if (x > n - 1) { // (n)_{_n} = n! if (x == n) { return(Math2.Factorial(n)); } // (n)_{_n+1} = n!, n>1 if (x == n + 1) { Debug.Assert(n > 1); return(Math2.Factorial(n + 1)); } return(Math2.TgammaDeltaRatio(x + 1, -n)); } Debug.Assert(x <= n - 1); if (x < 1) { int m = n - 1; double MaxFactorial = Math2.MaxFactorialIndex; if (m < MaxFactorial) { result = x / TgammaDeltaRatio(1 - x, m); } else { // try not to overflow/underflow too soon double m2 = m - MaxFactorial; result = (x / TgammaDeltaRatio(1 - x, MaxFactorial)) / TgammaDeltaRatio(1 - x + MaxFactorial, m2); } return(IsOdd(m) ? -result : result); } // // x+1-n will be negative and TgammaDeltaRatio won't // handle it, split the product up into three parts: // double xp1 = x + 1; int n2 = (int)xp1; if (n2 == xp1) { return(0); } result = Math2.TgammaDeltaRatio(xp1, -n2); x -= n2; result *= x; ++n2; if (n2 < n) { result *= FactorialFalling(x - 1, n - n2); } return(result); }
/// <summary> /// Returns the value of first derivative to the Airy Bi function at <paramref name="x"/> /// </summary> /// <param name="x">AiryBiPrime function argument</param> /// <returns></returns> public static double AiryBiPrime(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("AiryBiPrime(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { if (x < 0) { return(0); } return(double.PositiveInfinity); } double absX = Math.Abs(x); if (x >= -3 && x <= 3) { // Use small series. See: // http://functions.wolfram.com/Bessel-TypeFunctions/AiryBiPrime/26/01/01/ // http://dlmf.nist.gov/9.4 double z = x * x * x / 9; double s1 = x * x * (Constants.AiryBi0 / 2) * HypergeometricSeries.Sum0F1(5 / 3.0, z); double s2 = Constants.AiryBiPrime0 * HypergeometricSeries.Sum0F1(1 / 3.0, z); return(s1 + s2); } const double v = 2.0 / 3; double zeta = 2 * absX * Math.Sqrt(absX) / 3; if (x < 0) { if (x < -32) { return(AiryAsym.AiBiPrimeNeg(x).Bi); } // The following is //double j1 = Math2.BesselJ(v, zeta); //double j2 = Math2.BesselJ(-v, zeta); //double bip = -x * (j1 + j2) / Constants.Sqrt3; //return bip; var(J, Y) = _Bessel.JY(v, zeta, true, true); double s = 0.5 * (J / Constants.Sqrt3 - ((double)Y)); double bip = -x * s; return(bip); } else { if (x >= 16) { return(AiryAsym.BiPrime(x)); } // The following is //double j1 = Math2.BesselI(v, zeta); //double j2 = Math2.BesselI(-v, zeta); //double bip = (x / Constants.Sqrt3) * (j1 + j2); var(I, K) = _Bessel.IK(v, zeta, true, true); double s = (2 / Constants.Sqrt3 * I + K / Math.PI); var bip = x * s; return(bip); } }
/// <summary> /// Returns the rising factorial: /// <para>(x)_{n} = Γ(x+n)/Γ(x)</para> /// </summary> /// <returns> /// <para> if n > 0, x*(x+1)*(x+2)...*(x+(n-1))</para> /// <para> if n == 0, 1</para> /// <para> if n < 0, 1/((x-1)*(x-2)*...*(x-n))</para> /// </returns> public static double FactorialRising(double x, int n) { // This section uses the following notation: // falling factorial: (x)_{_n} // rising factorial: (x)_{n} // Standard eqn for the rising factorial: // (x)_{n} = Γ(x+n)/Γ(x) double result = double.NaN; // (x)_{0} = 1 if (n == 0) { return(1); } if (double.IsNaN(x)) { Policies.ReportDomainError("FactorialRising(x: {0}, n: {1}): Requires x not NaN", x, n); return(double.NaN); } if (n == int.MinValue) { // avoid -int.MinValue error // this will overflow/underflow, but here it is if (x > 0 && IsInteger(x) && (x + n) <= 0) { Policies.ReportPoleError("FactorialRising(x: {0}, n: {1}): Divide by Zero", x, n); return(double.NaN); } // (x)_{n} = (x)_{n+1}/(x+n) return(FactorialRising(x, n + 1) / (x + n)); } if (x == 0) { // (0)_{n} = 0, n > 0 if (n > 0) { return(0); } // (0)_{-n} = (-1)^n / n! result = Math2.Factorial(-n); if (IsOdd(n)) { result = -result; } return(1 / result); } if (x < 0) { // For x less than zero, we really have a falling // factorial, modulo a possible change of sign. // Note: the falling factorial isn't defined for negative n // so cover that first if (n < -x) { // handle (n < 0 || ( n > 0 && n < -x)) directly // (x)_{n} = Γ(1-x)/Γ(1-x-n) result = Math2.TgammaDeltaRatio(1 - x, -n); } else { Debug.Assert(n > 0); result = FactorialFalling(-x, n); } if (IsOdd(n)) { result = -result; } return(result); } // x > 0 // standard case: (n > 0 || (n < 0 && x > -n)) // (x)_{n} = Γ(x+n)/Γ(x) if (-x < n) { return(1 / Math2.TgammaDeltaRatio(x, n)); } Debug.Assert((x + n) <= 0); // (x)_{n} = (-1)^n * Γ(1-x)/Γ(1-x-n) if (x < 1) { result = TgammaDeltaRatio(1 - x, -n); if (IsOdd(n)) { result = -result; } return(result); } // x >= 1 result = FactorialFalling(x - 1, -n); if (result == 0) { if (IsInteger(x)) { Policies.ReportPoleError("FactorialRising(x: {0}, n: {1}): Divide by Zero", x, n); return(double.NaN); } return(double.PositiveInfinity); } return(1 / result); }
// Elliptic integrals (complete and incomplete) of the second kind // Carlson, Numerische Mathematik, vol 33, 1 (1979) /// <summary> /// Returns the incomplete elliptic integral of the second kind E(φ, k) /// <para>E(φ, k) = ∫ sqrt(1-k^2*sin^2(θ)) dθ, θ={0,φ}</para> /// </summary> /// <param name="k">The modulus. Requires |k| ≤ 1</param> /// <param name="phi">The amplitude</param> public static double EllintE(double k, double phi) { if ((!(k >= -1 && k <= 1)) || (double.IsNaN(phi))) { Policies.ReportDomainError("EllintE(k: {0}, phi: {1}): Requires |k| <= 1; phi not NaN", k, phi); return(double.NaN); } if (double.IsInfinity(phi)) { return(phi); } if (Math.Abs(phi) > Trig.PiReductionLimit) { Policies.ReportNotImplementedError("EllintE(k: {0}, phi: {1}): |phi| > {2} not implemented", k, phi, Trig.PiReductionLimit); return(double.NaN); } // special values if (k == 0) { return(phi); } if (phi == 0) { return(0); } if (phi == Math.PI / 2) { return(Math2.EllintE(k)); } // Carlson's algorithm works only for |phi| <= π/2, // use the integrand's periodicity to normalize phi // E(k, phi + π*mult) = E(k, phi) + 2*mult*E(k) double result = 0; double rphi = Math.Abs(phi); if (rphi > Math.PI / 2) { // Normalize periodicity so that |rphi| <= π/2 var(angleMultiple, angleRemainder) = Trig.RangeReducePI(rphi); double mult = 2 * angleMultiple; rphi = angleRemainder; if (mult != 0) { result += mult * EllintE(k); } } if (k == 1) { result += Math.Sin(rphi); } else { double k2 = k * k; double sinp = Math.Sin(rphi); double cosp = Math.Cos(rphi); double x = cosp * cosp; double t = k2 * sinp * sinp; double y = (t < 0.875) ? 1 - t : (1 - k2) + k2 * x; double z = 1; result += sinp * (Math2.EllintRF(x, y, z) - t * Math2.EllintRD(x, y, z) / 3); } return((phi < 0) ? -result : result); }
// Elliptic integrals (complete and incomplete) of the second kind // Carlson, Numerische Mathematik, vol 33, 1 (1979) /// <summary> /// Returns the incomplete elliptic integral of the second kind D(φ, k) /// <para>D(φ, k) = ∫ sin^2(θ)/sqrt(1-k^2*sin^2(θ)) dθ, θ={0,φ}</para> /// </summary> /// <param name="k">The modulus. Requires |k| ≤ 1</param> /// <param name="phi">The amplitude</param> public static double EllintD(double k, double phi) { if ((!(k >= -1 && k <= 1)) || (double.IsNaN(phi))) { Policies.ReportDomainError("EllintD(k: {0}, phi: {1}): Requires |k| <= 1; phi not NaN", k, phi); return(double.NaN); } if (double.IsInfinity(phi)) { return(phi); // Note: result != phi, only +/- infinity } if (Math.Abs(phi) > Trig.PiReductionLimit) { Policies.ReportNotImplementedError("EllintD(k: {0}, phi: {1}): |phi| > {2} not implemented", k, phi, Trig.PiReductionLimit); return(double.NaN); } // special values //if (k == 0) // too much cancellation error near phi=0, general case works fine // return (phi - Cos(phi)*Sin(phi))/2 if (phi == 0) { return(0); } if (phi == Math.PI / 2) { return(Math2.EllintD(k)); } // Carlson's algorithm works only for |phi| <= π/2, // use the integrand's periodicity to normalize phi // D(k, phi + π*mult) = D(k, phi) + 2*mult*D(k) double result = 0; double rphi = Math.Abs(phi); if (rphi > Math.PI / 2) { // Normalize periodicity so that |rphi| <= π/2 var(angleMultiple, angleRemainder) = Trig.RangeReducePI(rphi); double mult = 2 * angleMultiple; rphi = angleRemainder; if (mult != 0) { result += mult * EllintD(k); } } double k2 = k * k; double sinp = Math.Sin(rphi); double cosp = Math.Cos(rphi); double x = cosp * cosp; double t = k2 * sinp * sinp; double y = (t < 0.875) ? 1 - t : (1 - k2) + k2 * x; double z = 1; // http://dlmf.nist.gov/19.25#E13 // and RD(lambda*x, lambda*y, lambda*z) = lambda^(-3/2) * RD(x, y, z) result += EllintRD(x, y, z) * sinp * sinp * sinp / 3; return((phi < 0) ? -result : result); }