/// <summary> /// Returns the value of the Airy Ai function at <paramref name="x"/> /// </summary> /// <param name="x">The function argument</param> /// <returns></returns> public static double AiryAi(double x) { if (double.IsNaN(x)) { Policies.ReportDomainError("AiryAi(x: {0}): NaN not allowed", x); return(double.NaN); } if (double.IsInfinity(x)) { return(0); } double absX = Math.Abs(x); if (x >= -3 && x <= 2) { // Use small series. See: // http://functions.wolfram.com/Bessel-TypeFunctions/AiryAi/06/01/02/01/0004/ // http://dlmf.nist.gov/9.4 double z = x * x * x / 9; double s1 = Constants.AiryAi0 * HypergeometricSeries.Sum0F1(2 / 3.0, z); double s2 = x * Constants.AiryAiPrime0 * HypergeometricSeries.Sum0F1(4 / 3.0, z); return(s1 + s2); } const double v = 1.0 / 3; double zeta = 2 * absX * Math.Sqrt(absX) / 3; if (x < 0) { if (x < -32) { return(AiryAsym.AiBiNeg(x).Ai); } // The following is //double j1 = Math2.BesselJ(v, zeta); //double j2 = Math2.BesselJ(-v, zeta); //double ai = Math.Sqrt(-x) * (j1 + j2) / 3; var(J, Y) = _Bessel.JY(v, zeta, true, true); double s = 0.5 * (J - ((double)Y) / Constants.Sqrt3); double ai = Math.Sqrt(-x) * s; return(ai); } else { // Use the K relationship: http://dlmf.nist.gov/9.6 if (x >= 16) { return(AiryAsym.Ai(x)); } double ai = Math2.BesselK(v, zeta) * Math.Sqrt(x / 3) / Math.PI; return(ai); } }
/// <summary> /// Returns the Spherical Bessel function of the first kind: j<sub>n</sub>(x) /// </summary> /// <param name="n">Order. Requires n >= 0</param> /// <param name="x">Argument. Requires x >= 0</param> /// <returns></returns> public static double SphBesselJ(int n, double x) { if (!(n >= 0) || !(x >= 0)) { Policies.ReportDomainError("SphBesselJ(n: {0}, x: {1}): Requires n >= 0, x >= 0", n, x); return(double.NaN); } if (double.IsInfinity(x)) { return(0); } // // Special case, n == 0 resolves down to the sinus cardinal of x: // if (n == 0) { return(Math2.Sinc(x)); } if (x < 1) { // the straightforward evaluation below can underflow too quickly when x is small double result = (0.5 * Constants.SqrtPI); result *= (Math.Pow(x / 2, n) / Math2.Tgamma(n + 1.5)); if (result != 0) { result *= HypergeometricSeries.Sum0F1(n + 1.5, -x * x / 4); } return(result); } // Default case is just a straightforward evaluation of the definition: return(Constants.SqrtHalfPI * BesselJ(n + 0.5, x) / Math.Sqrt(x)); }
/// <summary> /// Returns Iv(x) for small <paramref name="x"/> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static double I_SmallArg(double v, double x) { double prefix; if (v <= DoubleLimits.MaxGamma - 1) { prefix = Math.Pow(x / 2, v) / Math2.Tgamma(v + 1); } else { prefix = StirlingGamma.PowDivGamma(x / 2, v) / v; } if (prefix == 0) return prefix; double series = HypergeometricSeries.Sum0F1(v + 1, x * x / 4); return prefix * series; }
/// <summary> /// Series evaluation for Jv(x), for small x /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> public static double J_SmallArg(double v, double x) { Debug.Assert(v >= 0); // See http://functions.wolfram.com/Bessel-TypeFunctions/BesselJ/06/01/04/01/01/0003/ // Converges rapidly for all x << v. // Most of the error occurs calculating the prefix double prefix; if (v <= DoubleLimits.MaxGamma - 1) prefix = Math.Pow(x / 2, v) / Math2.Tgamma(v + 1); else prefix = StirlingGamma.PowDivGamma(x / 2, v)/v; if (prefix == 0) return prefix; double series = HypergeometricSeries.Sum0F1(v + 1, -x * x / 4); return prefix * series; }
// // Series form for BesselY as z -> 0, // see: http://functions.wolfram.com/Bessel-TypeFunctions/BesselY/06/01/04/01/01/0003/ // This series is only useful when the second term is small compared to the first // otherwise we get catestrophic cancellation errors. // // Approximating tgamma(v) by v^v, and assuming |tgamma(-z)| < eps we end up requiring: // eps/2 * v^v(x/2)^-v > (x/2)^v or Math.Log(eps/2) > v Math.Log((x/2)^2/v) // /// <summary> /// Series form for Y{v}(x) as z -> 0 and v is not an integer /// <para>Y{v}(z) = (-((z/2)^v Cos(πv) Γ(-v))/π)) * 0F1(; 1+v; -z^2/4) - ((z/2)^-v Γ(v))/π) * 0F1(; 1-v; -z^2/4)</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> /// <seealso href="http://functions.wolfram.com/Bessel-TypeFunctions/BesselY/26/01/02/0001/"/> public static DoubleX Y_SmallArgSeries(double v, double x) { Debug.Assert(v >= 0 && x >= 0); Debug.Assert( !Math2.IsInteger(v), "v cannot be an integer"); DoubleX powDivGamma; double lnp = v * Math.Log(x / 2); if (v > DoubleLimits.MaxGamma || lnp < DoubleLimits.MinLogValue || lnp > DoubleLimits.MaxLogValue) { powDivGamma = DoubleX.Exp(lnp - Math2.Lgamma(v)); } else { DoubleX p = Math.Pow(x / 2, v); DoubleX gam = Math2.Tgamma(v); powDivGamma = p / gam; } // Series 1: -((z/2)^-v Γ(v))/π) * 0F1(; 1-v; -z^2/4) double s1 = HypergeometricSeries.Sum0F1(1 - v, -x * x / 4); DoubleX result1 = -(s1/Math.PI)/ powDivGamma; // Series2: -((z/2)^v Cos(πv) Γ(-v))/π)) * 0F1(; 1+v; -z^2/4) // using the reflection formula: Γ(-v) = -π/(Γ(v) * v * Sin(π * v)) // prefix = (z/2)^v * Cos(π * v)/(Γ(v) * v * Sin(π * v)) // prefix = (z/2)^v * Cot(π * v) / (Γ(v) * v) DoubleX result2 = DoubleX.Zero; double cot = Math2.CotPI(v); if (cot != 0) { double s2 = HypergeometricSeries.Sum0F1(v + 1, -x * x / 4); result2 = powDivGamma * cot * s2 / v; // Do this all in DoubleX } return (result1 - result2); }
/// <summary> /// Returns the 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 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); } }