/// <summary> /// Returns the value of the Legendre Polynomial of the first kind /// </summary> /// <param name="n">Polynomial degree. Limited to |n| ≤ 127</param> /// <param name="x">Requires |x| ≤ 1</param> public static double LegendreP(int n, double x) { if (!(x >= -1 && x <= 1)) { Policies.ReportDomainError("LegendreP(n: {0}, x: {1}): Requires |x| <= 1", n, x); return(double.NaN); } if (n <= -128 || n >= 128) { Policies.ReportNotImplementedError("LegendreP(n: {0}, x: {1}): Not implemented for |n| >= 128", n, x); return(double.NaN); } // LegendreP(n,x) == LegendreP(-n-1,x) if (n < 0) { n = -(n + 1); } // Special values if (n == 0) { return(1); } if (x == 1) { return(1); } if (x == -1) { return((IsOdd(n)) ? -1 : 1); } // Implement Legendre P polynomials via recurrance // p0 - current // p1 - next double p0 = 1; double p1 = x; for (uint k = 1; k < (uint)n; k++) { double next = ((2 * k + 1) * x * p1 - k * p0) / (k + 1); p0 = p1; p1 = next; } return(p1); }
/// <summary> /// Returns the value of the Associated Laguerre polynomial of degree <paramref name="n"/> and order <paramref name="m"/> at point <paramref name="x"/> /// </summary> /// <param name="n">Polynomial degree. Requires <paramref name="n"/> ≥ 0. Limited to <paramref name="n"/> ≤ 127</param> /// <param name="m">Polynomial order. Requires <paramref name="m"/> ≥ 0</param> /// <param name="x">Polynomial argument</param> public static double LaguerreL(int n, int m, double x) { if ((n < 0) || (m < 0)) { Policies.ReportDomainError("LaguerreL(n: {0}, m: {1}, x: {2}): Requires n,m >= 0", n, m, x); return(double.NaN); } if (n >= 128) { Policies.ReportNotImplementedError("LaguerreL(n: {0}, m: {1}, x: {2}): Not implemented for n >= 128", n, m, x); return(double.NaN); } // Special cases: if (m == 0) { return(Math2.LaguerreL(n, x)); } if (n == 0) { return(1); } if (double.IsNaN(x)) { Policies.ReportDomainError("LaguerreL(n: {0}, m: {1}, x: {2}): NaN not allowed", n, m, x); return(double.NaN); } if (double.IsInfinity(x)) { return((x > 0 && IsOdd(n)) ? double.NegativeInfinity : double.PositiveInfinity); } // use recurrence // p0 - current // p1 - next double p0 = 1; double p1 = m + 1 - x; for (uint k = 1; k < (uint)n; k++) { double next = ((2 * k + (uint)m + 1 - x) * p1 - (k + (uint)m) * p0) / (k + 1); p0 = p1; p1 = next; } return(p1); }
/// <summary> /// Returns a pair representing x/(π/2) ensuring that |remainder| ≤ π/4 /// <para>Multiple = Sign(x) * Floor(|x|/(π/2) + 1/2)</para> /// <para>Remainder = Sign(x) * (|x|-(π/2)*Floor(|x|/(π/2) + 1/2))</para> /// </summary> public static (double Multiple, double Remainder) RangeReduceHalfPI(double x) { if (double.IsNaN(x) || double.IsInfinity(x)) { Policies.ReportDomainError("RangeReduceHalfPI(x: {0}) Requires finite argument", x); return(double.NaN, double.NaN); } double absX = Math.Abs(x); if (absX > HalfPi.MaxLimit) { Policies.ReportNotImplementedError("RangeReduceHalfPI(x: {0}) |x| > {1} is not implemented", x, HalfPi.MaxLimit); return(double.NaN, double.NaN); } if (absX < HalfPi.Value / 2) { return(0, x); } double k = Math.Floor(absX * HalfPi.Reciprocal + 0.5); double rem; for (; ;) { rem = ((absX - (k * HalfPi.A)) - k * HalfPi.B) - k * HalfPi.C; if (rem >= -HalfPi.Value / 2) { break; } k--; } if (x < 0) { k = -k; rem = -rem; } Debug.Assert(Math.Abs(rem) <= HalfPi.Value / 2); return(k, rem); }
/// <summary> /// Returns the value of the Legendre polynomial that is the second solution to the Legendre differential equation /// </summary> /// <param name="n">Polynomial degree. Requires n ≥ 0. Limited to n ≤ 127</param> /// <param name="x">Requires |x| ≤ 1</param> public static double LegendreQ(int n, double x) { if ((n < 0) || (!(x >= -1 && x <= 1))) { Policies.ReportDomainError("LegendreQ(n: {0}, x: {1}): Requires n >= 0; |x| <= 1", n, x); return(double.NaN); } if (x == 1) { return(double.PositiveInfinity); } if (x == -1) { return(IsOdd(n) ? double.PositiveInfinity : double.NegativeInfinity); } if (n >= 128) { Policies.ReportNotImplementedError("LegendreQ(n: {0}, x: {1}): Not implemented for n >= 128", n, x); return(double.NaN); } // Implement Legendre Q polynomials via recurrance // p0 - current // p1 - next double p0 = Math2.Atanh(x); double p1 = x * p0 - 1; if (n == 0) { return(p0); } for (uint k = 1; k < (uint)n; k++) { double next = ((2 * k + 1) * x * p1 - k * p0) / (k + 1); p0 = p1; p1 = next; } return(p1); }
/// <summary> /// Returns the incomplete elliptic integral of the third kind Π(n, φ, k) /// <para>Π(n, φ, k) = ∫ dθ/((1-n^2*sin^2(θ)) * sqrt(1-k^2*sin^2(θ))), θ={0,φ}</para> /// </summary> /// <param name="k">The modulus. Requires |k| ≤ 1</param> /// <param name="nu">The characteristic. Requires nu < 1/sin^2(φ)</param> /// <param name="phi">The amplitude</param> /// <remarks> /// Perhaps more than any other special functions the elliptic integrals are expressed in a variety of different ways. /// In particular, the final parameter k (the modulus) may be expressed using a modular angle α, or a parameter m. /// These are related by: /// <para>k = sin α</para> /// <para>m = k^2 = (sin α)^2</para> /// So that the incomplete integral of the third kind may be expressed as either: /// <para>Π(n, φ, k)</para> /// <para>Π(n, φ \ α)</para> /// <para>Π(n, φ| m)</para> /// To further complicate matters, some texts refer to the complement of the parameter m, or 1 - m, where: /// 1 - m = 1 - k^2 = (cos α)^2 /// </remarks> public static double EllintPi(double k, double nu, double phi) { if ((!(k >= -1 && k <= 1)) || (double.IsNaN(nu) || double.IsNaN(phi))) { Policies.ReportDomainError("EllintPi(k: {0}, nu: {1}, phi: {2}): Requires |k| <= 1; No NaNs", k, nu, phi); return(double.NaN); } if (double.IsInfinity(phi)) { return(phi); } if (Math.Abs(phi) > Trig.PiReductionLimit) { Policies.ReportNotImplementedError("EllintPi(k: {0}, nu: {1}, phi: {2}): |phi| > {3} not implemented", k, nu, phi, Trig.PiReductionLimit); return(double.NaN); } return(_EllintPi.Imp(k, nu, 1 - nu, phi)); }
/// <summary> /// Returns the Spherical Harmonic Y{n,m}(theta,phi = 0) /// <para>Y{n,m}(theta,0) = Sqrt((2*n+1)/(4*π) * (n-m)!/(n+m)!)* P{n,m}(cos(theta))</para> /// </summary> /// <param name="n">Polynomial degree. Requires <paramref name="n"/> ≥ 0. Limited to <paramref name="n"/> ≤ 127</param> /// <param name="m">Polynomial order</param> /// <param name="theta">Colatitude, or polar angle, [0,π] where 0=North Pole, π=South Pole, π/2=Equator</param> public static double SphLegendre(int n, int m, double theta) { if ((n < 0) || (double.IsNaN(theta) || double.IsInfinity(theta))) { Policies.ReportDomainError("SphLegendre(n: {0}, m: {1}, theta: {2}): Requires n >= 0; finite theta", n, m, theta); return(double.NaN); } // by definition: if ( abs(m) < n ) return 0 if (m < -n || m > n) { return(0); } if (n >= 128) { Policies.ReportNotImplementedError("SphLegendre(n: {0}, m: {1}, theta: {2}): Not implemented for n >= 128", n, m, theta); return(double.NaN); } bool sign = false; if (m < 0) { sign = IsOdd(m); m = Math.Abs(m); } // at this point n >= 0 && 0 <= m <= n Debug.Assert(n >= 0 && (m >= 0 && m <= n)); Debug.Assert(n < 128); double result; double legP = LegendrePAngle(n, m, theta); int MaxFactorial = Math2.FactorialTable.Length - 1; if (n + m <= MaxFactorial) { double ratio = Math2.FactorialTable[n - m] / Math2.FactorialTable[n + m]; result = 0.5 * Constants.RecipSqrtPI * Math.Sqrt((2 * n + 1) * ratio) * legP; } else if (n + m <= 300) { // break up the Tgamma ratio into two separate factors // to prevent an underflow: a!/b! = (a!/170!) * (170!/b!) // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue double ratio1 = Math2.FactorialTable[n - m] / Math2.FactorialTable[MaxFactorial]; double ratio2 = Math2.TgammaRatio(MaxFactorial + 1, n + m + 1); result = 0.5 * Constants.RecipSqrtPI * Math.Sqrt(ratio1) * Math.Sqrt((2 * n + 1) * ratio2) * legP; } else { // use logs // note: with a limit of n=128, we should never reach here, but it is left for completeness double ratio = Math2.TgammaRatio(n - m + 1, n + m + 1); if (ratio < DoubleLimits.MinNormalValue) { double mult = (2 * n + 1) / (4 * Math.PI); result = Math.Exp((Math2.Lgamma(n - m + 1) - Math2.Lgamma(n + m + 1) + Math.Log(mult)) / 2 + Math.Log(Math.Abs(legP))); result *= Math.Sign(legP); } else { result = 0.5 * Constants.RecipSqrtPI * Math.Sqrt((2 * n + 1) * ratio) * legP; } } return(sign ? -result : result); }
// // Spherical // /// <summary> /// Returns the associated legendre polynomial with angle parameterization P{l,m}( cos(theta) ) /// </summary> /// <param name="n"></param> /// <param name="m"></param> /// <param name="theta"></param> /// <returns></returns> static double LegendrePAngle(int n, int m, double theta) { if (n < 0) { n = -(n + 1); // P{n,m}(x) = P{-n-1,m}(x) } if (m == 0) { return(Math2.LegendreP(n, Math.Cos(theta))); } // if ( abs(m) < n ) return 0 by definition; handling int.MinValue too if (m < -n || m > n) { return(0); } // P{n,-m} = (-1)^m * (n-m)!/(n+m)! * P{n,m} if (m < 0) { double legP = LegendrePAngle(n, -m, theta); if (legP == 0) { return(legP); } if (IsOdd(m)) { legP = -legP; } int MaxFactorial = Math2.FactorialTable.Length - 1; if (n - m <= MaxFactorial) { double ratio = Math2.FactorialTable[n + m] / Math2.FactorialTable[n - m]; return(ratio * legP); } if (n - m <= 300) { // break up the Tgamma ratio into two separate factors // to prevent an underflow: a!/b! = (a!/170!) * (170!/b!) // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue return((legP * (Math2.FactorialTable[n + m] / Math2.FactorialTable[MaxFactorial])) * Math2.TgammaRatio(MaxFactorial + 1, n + 1 - m)); } #if false // since we are setting Lmax = 128, so Mmax = 128, and Lmax+Mmax = 256 // so this should never be called, but left here for completeness // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue double ratio = Math2.TgammaRatio(l + m + 1, l + 1 - m); if (ratio >= DoubleLimits.MinNormalizedValue) { return(ratio * legP); } double value = Math.Exp(Math2.Lgamma(l + m + 1) - Math2.Lgamma(l + 1 - m) + Math.Log(Math.Abs(legP))); return(Math.Sign(legP) * value); #else Policies.ReportNotImplementedError("LegendrePAngle(l: {0}, m: {1}, theta: {2}): Not implemented for |l| >= 128", n, m, theta); return(double.NaN); #endif } // at this point n >= 0 && 0 <= m <= n Debug.Assert(n >= 0 && (m >= 0 && m <= n)); // use the recurrence relations // P{n, n} = (-1)^n * (2*n - 1)!! * (1 - x^2)^(n/2) // P{n+1, n} = (2*n + 1) * x * P{n, n} // (n-m+1)*P{n+1,m} = (2*n + 1)* x * P{n,m} - (n+m)*P{n-1, m} // see: http://en.wikipedia.org/wiki/Associated_Legendre_polynomials double x = Math.Cos(theta); double sin_theta = Math.Sin(theta); // the following computes (2m-1)!! * sin_theta^m // taking special care not to underflow too soon // double pmm = Math.Pow(sin_theta, m) * Math2.Factorial2(2 * m - 1) int exp = 0; double mant = Math2.Frexp(sin_theta, out exp); double pmmScaled = Math.Pow(mant, m) * Math2.Factorial2(2 * m - 1); double pmm = Math2.Ldexp(pmmScaled, m * exp); if (IsOdd(m)) { pmm = -pmm; } if (n == m) { return(pmm); } // p0 - current // p1 - next double p0 = pmm; double p1 = (2.0 * m + 1.0) * x * p0; //P{m+1,m}(x) for (uint k = (uint)m + 1; k < (uint)n; k++) { double next = ((2 * k + 1) * x * p1 - (k + (uint)m) * p0) / (k - (uint)m + 1); p0 = p1; p1 = next; } return(p1); }
/// <summary> /// Returns the Associated Legendre Polynomial of the first kind /// </summary> /// <param name="n">Polynomial degree. Limited to |<paramref name="n"/>| ≤ 127</param> /// <param name="m">Polynomial order</param> /// <param name="x">Requires |<paramref name="x"/>| ≤ 1</param> public static double LegendreP(int n, int m, double x) { if (!(x >= -1 && x <= 1)) { Policies.ReportDomainError("LegendreP(n: {0}, m: {1}, x: {2}): Requires |x| <= 1", n, m, x); return(double.NaN); } if (n <= -128 || n >= 128) { Policies.ReportNotImplementedError("LegendreP(n: {0}, m: {1}, x: {2}): Not implemented for |n| >= 128", n, m, x); return(double.NaN); } // P{l,m}(x) == P{-l-1,m}(x); watch overflow if (n < 0) { n = -(n + 1); } if (m == 0) { return(Math2.LegendreP(n, x)); } // by definition P{n,m}(x) == 0 when |m| > l if (m < -n || m > n) { return(0); } // P{n,-m} = (-1)^m * (n-m)!/(n+m)! * P{n,m} if (m < 0) { double legP = LegendreP(n, -m, x); if (legP == 0) { return(legP); } if (IsOdd(m)) { legP = -legP; } int MaxFactorial = Math2.FactorialTable.Length - 1; if (n - m <= MaxFactorial) { double ratio = Math2.FactorialTable[n + m] / Math2.FactorialTable[n - m]; return(ratio * legP); } if (n - m <= 300) { // break up the Tgamma ratio into two separate factors // to prevent an underflow: a!/b! = (a!/170!) * (170!/b!) // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue return((legP * (Math2.FactorialTable[n + m] / Math2.FactorialTable[MaxFactorial])) * Math2.TgammaRatio(MaxFactorial + 1, n + 1 - m)); } #if false // since we are setting Nmax = 128, so Mmax = 128, and Nmax+Mmax = 256 // so this should never be called, but left here for completeness // 170!/300! ~= 2.37e-308 > double.MinNormalizedValue double ratioL = Math2.TgammaRatio(n + m + 1, n + 1 - m); if (ratioL >= DoubleLimits.MinNormalizedValue) { return(ratioL * legP); } double value = Math.Exp(Math2.Lgamma(n + m + 1) - Math2.Lgamma(n + 1 - m) + Math.Log(Math.Abs(legP))); return(Math.Sign(legP) * value); #else Policies.ReportNotImplementedError("LegendreP(n: {0}, m: {1}, x: {2}): Not implemented for |n| >= 128", n, m, x); return(double.NaN); #endif } // at this point n >= 0 && 0 <= m <= n Debug.Assert(n >= 0 && (m >= 0 && m <= n)); // use the following recurrence relations: // P{n, n} = (-1)^n * (2*n - 1)!! * (1 - x^2)^(n/2) // P{n+1, n} = (2*n + 1) * x * P{n, n} // (n-m+1)*P{n+1,m} = (2*n + 1)* x * P{n,m} - (n+m)*P{n-1, m} // see: http://en.wikipedia.org/wiki/Associated_Legendre_polynomials // the following is: // pmm = Math2.Factorial2(2 * m - 1) * Math.Pow(1.0 - x*x, m/2.0); // being careful not to underflow too soon double pmm = 0; if (Math.Abs(x) > Constants.RecipSqrt2) { double inner = (1 - x) * (1 + x); int exp = 0; double mant = Math2.Frexp(inner, out exp); // careful: use integer division double pmm_scaled = Math.Pow(mant, m / 2) * Math2.Factorial2(2 * m - 1); pmm = Math2.Ldexp(pmm_scaled, (m / 2) * exp); if (IsOdd(m)) { pmm *= Math.Sqrt(inner); } } else { double p_sin_theta = Math.Exp(m / 2.0 * Math2.Log1p(-x * x)); //Math.Pow(1.0 - x*x, m/2.0); pmm = Math2.Factorial2(2 * m - 1) * p_sin_theta; } if (IsOdd(m)) { pmm = -pmm; } if (n == m) { return(pmm); } // p0 - current // p1 - next double p0 = pmm; double p1 = (2.0 * m + 1.0) * x * p0; //P{m+1,m}(x) for (uint k = (uint)m + 1; k < (uint)n; k++) { double next = ((2 * k + 1) * x * p1 - (k + (uint)m) * p0) / (k - (uint)m + 1); p0 = p1; p1 = next; } return(p1); }
// Elliptic integrals (complete and incomplete) of the first kind // Carlson, Numerische Mathematik, vol 33, 1 (1979) /// <summary> /// Returns the incomplete elliptic integral of the first kind /// <para>F(φ, k) = F(φ | k^2) = F(sin(φ); k)</para> /// <para>F(φ, k) = ∫ dθ/sqrt(1-k^2*sin^2(θ)), θ={0,φ}</para> /// <para>F(x; k) = ∫ dt/sqrt((1-t^2)*(1-k^2*t^2)), t={0,x}</para> /// </summary> /// <param name="k">The modulus. Requires |k| ≤ 1</param> /// <param name="phi">The amplitude</param> public static double EllintF(double k, double phi) { if ((!(k >= -1 && k <= 1)) || (double.IsNaN(phi))) { Policies.ReportDomainError("EllintF(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("EllintF(k: {0}, phi: {1}): |phi| > {2} not implemented", k, phi, Trig.PiReductionLimit); return(double.NaN); } #if EXTRA_DEBUG Debug.WriteLine("EllintF(phi = {0}; k = {1})", phi, k); #endif // special values if (k == 0) { return(phi); } if (phi == 0) { return(0); } if (phi == Math.PI / 2) { return(Math2.EllintK(k)); } // Carlson's algorithm works only for |phi| <= π/2, // use the integrand's periodicity to normalize phi // F(k, phi + π*mult) = F(k, phi) + 2*mult*K(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 * EllintK(k); } } double sinp = Math.Sin(rphi); double cosp = Math.Cos(rphi); double k2 = k * k; double x = cosp * cosp; double y = 1 - k2 * sinp * sinp; if (y > 0.125) // use a more accurate form with less cancellation { y = (1 - k2) + k2 * x; } double z = 1; result += sinp * EllintRF(x, y, z); 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 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); }
/// <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> /// Simultaneously compute I{v}(x) and K{v}(x) /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needI"></param> /// <param name="needK"></param> /// <returns>A tuple of (I, K). If needI == false or needK == false, I = NaN or K = NaN respectively</returns> public static (double I, double K) IK(double v, double x, bool needI, bool needK) { Debug.Assert(needI || needK, "NeedI and NeedK cannot both be false"); // set initial values for parameters double I = double.NaN; double K = double.NaN; // check v first so that there are no integer throws later if (Math.Abs(v) > int.MaxValue) { Policies.ReportNotImplementedError("BesselIK(v: {0}, x: {1}): Requires |v| <= {2}", v, x, int.MaxValue); return(I, K); } if (v < 0) { v = -v; // for integer orders only, we can use the following identities: // I{-n}(x) = I{n}(v) // K{-n}(x) = K{n}(v) // for any v, use reflection rule: // I{-v}(x) = I{v}(x) + (2/PI) * sin(pi*v)*K{v}(x) // K{-v}(x) = K{v}(x) if (needI && !Math2.IsInteger(v)) { var(IPos, KPos) = IK(v, x, true, true); I = IPos + (2 / Math.PI) * Math2.SinPI(v) * KPos; if (needK) { K = KPos; } return(I, K); } } if (x < 0) { Policies.ReportDomainError("BesselIK(v: {0}, x: {1}): Complex result not supported. Requires x >= 0", v, x); return(I, K); } // both x and v are non-negative from here Debug.Assert(x >= 0 && v >= 0); if (x == 0) { if (needI) { I = (v == 0) ? 1.0 : 0.0; } if (needK) { K = double.PositiveInfinity; } return(I, K); } if (needI && (x < 2 || 3 * (v + 1) > x * x)) { I = I_SmallArg(v, x); needI = false; if (!needK) { return(I, K); } } // Hankel is fast, and reasonably accurate. // at x == 32, it will converge in about 15 iterations if (x >= HankelAsym.IKMinX(v)) { if (needK) { K = HankelAsym.K(v, x); } if (needI) { I = HankelAsym.I(v, x); } return(I, K); } // the uniform expansion is here as a last resort // to limit the number of recurrences, but it is less accurate. if (UniformAsym.IsIKAvailable(v, x)) { if (needK) { K = UniformAsym.K(v, x); } if (needI) { I = UniformAsym.I(v, x); } return(I, K); } // K{v}(x) and K{v+1}(x), binary scaled var(Kv, Kv1, binaryScale) = K_CF(v, x); if (needK) { K = Math2.Ldexp(Kv, binaryScale); } if (needI) { // use the Wronskian relationship // Since CF1 is O(x) for x > v, try to weed out obvious overflows // Note: I{v+1}(x)/I{v}(x) is in [0, 1]. // I{0}(713. ...) == MaxDouble const double I0MaxX = 713; if (x > I0MaxX && x > v) { I = Math2.Ldexp(1.0 / (x * (Kv + Kv1)), -binaryScale); if (double.IsInfinity(I)) { return(I, K); } } double W = 1 / x; double fv = I_CF1(v, x); I = Math2.Ldexp(W / (Kv * fv + Kv1), -binaryScale); } return(I, K); }
/// <summary> /// Returns J{v}(x) for integer v /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static double JN(double v, double x) { Debug.Assert(Math2.IsInteger(v)); // For integer orders only, we can use two identities: // #1: J{-n}(x) = (-1)^n * J{n}(x) // #2: J{n}(-x) = (-1)^n * J{n}(x) double sign = 1; if (v < 0) { v = -v; if (Math2.IsOdd(v)) { sign = -sign; } } if (x < 0) { x = -x; if (Math2.IsOdd(v)) { sign = -sign; } } Debug.Assert(v >= 0 && x >= 0); if (v > int.MaxValue) { Policies.ReportNotImplementedError("BesselJ(v: {0}): Large integer values not yet implemented", v); return(double.NaN); } int n = (int)v; // // Special cases: // if (n == 0) { return(sign * J0(x)); } if (n == 1) { return(sign * J1(x)); } // n >= 2 Debug.Assert(n >= 2); if (x == 0) { return(0); } if (x < 5 || (v > x * x / 4)) { return(sign * J_SmallArg(v, x)); } // if v > MinAsymptoticV, use the asymptotics const int MinAsymptoticV = 7; // arbitrary constant to reduce iterations if (v > MinAsymptoticV) { double Jv; DoubleX Yv; if (JY_TryAsymptotics(v, x, out Jv, out Yv, true, false)) { return(sign * Jv); } } // v < abs(x), forward recurrence stable and usable // v >= abs(x), forward recurrence unstable, use Miller's algorithm if (v < x) { // use forward recurrence double prev = J0(x); double current = J1(x); return(sign * Recurrence.ForwardJY(1.0, x, n - 1, current, prev).JYvpn); } else { // Steed is somewhat more accurate when n gets large if (v >= 200) { var(J, Y) = JY_Steed(n, x, true, false); return(J); } // J{n+1}(x) / J{n}(x) var(fv, s) = J_CF1(n, x); var(Jvmn, Jvmnp1, scale) = Recurrence.BackwardJY_B(n, x, n, s, fv * s); // scale the result double Jv = (J0(x) / Jvmn); Jv = Math2.Ldexp(Jv, -scale); return((s * sign) * Jv); // normalization } }
// 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); }
/// <summary> /// Compute J{v}(x) and Y{v}(x) /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needJ"></param> /// <param name="needY"></param> /// <returns></returns> public static (double J, DoubleX Y) JY(double v, double x, bool needJ, bool needY) { Debug.Assert(needJ || needY); // uses Steed's method // see Barnett et al, Computer Physics Communications, vol 8, 377 (1974) // set [out] parameters double J = double.NaN; DoubleX Y = DoubleX.NaN; // check v first so that there are no integer throws later if (Math.Abs(v) > int.MaxValue) { Policies.ReportNotImplementedError("BesselJY(v: {0}): Large |v| > int.MaxValue not yet implemented", v); return(J, Y); } if (v < 0) { v = -v; if (Math2.IsInteger(v)) { // for integer orders only, we can use the following identities: // J{-n}(x) = (-1)^n * J{n}(x) // Y{-n}(x) = (-1)^n * Y{n}(x) if (Math2.IsOdd(v)) { var(JPos, YPos) = JY(v, x, needJ, needY); if (needJ) { J = -JPos; } if (needY) { Y = -YPos; } return(J, Y); } } if (v - Math.Floor(v) == 0.5) { Debug.Assert(v >= 0); // use reflection rule: // for integer m >= 0 // J{-(m+1/2)}(x) = (-1)^(m+1) * Y{m+1/2}(x) // Y{-(m+1/2)}(x) = (-1)^m * J{m+1/2}(x) // call the general bessel functions with needJ and needY reversed var(JPos, YPos) = JY(v, x, needY, needJ); double m = v - 0.5; bool isOdd = Math2.IsOdd(m); if (needJ) { double y = (double)YPos; J = isOdd ? y : -y; } if (needY) { Y = isOdd ? -JPos : JPos; } return(J, Y); } else { // use reflection rule: // J{-v}(x) = cos(pi*v)*J{v}(x) - sin(pi*v)*Y{v}(x) // Y{-v}(x) = sin(pi*v)*J{v}(x) + cos(pi*v)*Y{v}(x) var(JPos, YPos) = JY(v, x, true, true); double cp = Math2.CosPI(v); double sp = Math2.SinPI(v); J = cp * JPos - (double)(sp * YPos); Y = sp * JPos + cp * YPos; return(J, Y); } } // both x and v are positive from here Debug.Assert(x >= 0 && v >= 0); if (x == 0) { // For v > 0 if (needJ) { J = 0; } if (needY) { Y = DoubleX.NegativeInfinity; } return(J, Y); } int n = (int)Math.Floor(v + 0.5); double u = v - n; // -1/2 <= u < 1/2 // is it an integer? if (u == 0) { if (v == 0) { if (needJ) { J = J0(x); } if (needY) { Y = Y0(x); } return(J, Y); } if (v == 1) { if (needJ) { J = J1(x); } if (needY) { Y = Y1(x); } return(J, Y); } // for integer order only if (needY && x < DoubleLimits.RootMachineEpsilon._2) { Y = YN_SmallArg(n, x); if (!needJ) { return(J, Y); } needY = !needY; } } if (needJ && ((x < 5) || (v > x * x / 4))) { // always use the J series if we can J = J_SmallArg(v, x); if (!needY) { return(J, Y); } needJ = !needJ; } if (needY && x <= 2) { // J should have already been solved above Debug.Assert(!needJ); // Evaluate using series representations. // Much quicker than Y_Temme below. // This is particularly important for x << v as in this // area Y_Temme may be slow to converge, if it converges at all. // for non-integer order only if (u != 0) { if ((x < 1) && (Math.Log(DoubleLimits.MachineEpsilon / 2) > v * Math.Log((x / 2) * (x / 2) / v))) { Y = Y_SmallArgSeries(v, x); return(J, Y); } } // Use Temme to find Yu where |u| <= 1/2, then use forward recurrence for Yv var(Yu, Yu1) = Y_Temme(u, x); var(Yvpn, Yvpnm1, YScale) = Recurrence.ForwardJY_B(u + 1, x, n, Yu1, Yu); Y = DoubleX.Ldexp(Yvpnm1, YScale); return(J, Y); } Debug.Assert(x > 2 && v >= 0); // Try asymptotics directly: if (x > v) { // x > v*v if (x >= HankelAsym.JYMinX(v)) { var result = HankelAsym.JY(v, x); if (needJ) { J = result.J; } if (needY) { Y = result.Y; } return(J, Y); } // Try Asymptotic Phase for x > 47v if (x >= MagnitudePhase.MinX(v)) { var result = MagnitudePhase.BesselJY(v, x); if (needJ) { J = result.J; } if (needY) { Y = result.Y; } return(J, Y); } } // fast and accurate within a limited range of v ~= x if (UniformAsym.IsJYPrecise(v, x)) { var(Jv, Yv) = UniformAsym.JY(v, x); if (needJ) { J = Jv; } if (needY) { Y = Yv; } return(J, Y); } // Try asymptotics with recurrence: if (x > v && x >= HankelAsym.JYMinX(v - Math.Floor(v) + 1)) { var(Jv, Yv) = JY_AsymRecurrence(v, x, needJ, needY); if (needJ) { J = Jv; } if (needY) { Y = Yv; } return(J, Y); } // Use Steed's Method var(SteedJv, SteedYv) = JY_Steed(v, x, needJ, needY); if (needJ) { J = SteedJv; } if (needY) { Y = SteedYv; } return(J, Y); }