/// <summary> /// Compute (J, Y) using Steeds method /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needJ"></param> /// <param name="needY"></param> /// <returns></returns> static (double J, DoubleX Y) JY_Steed(double v, double x, bool needJ, bool needY) { Debug.Assert(x > 0 && v >= 0); Debug.Assert(needJ || needY); double J = double.NaN; DoubleX Y = DoubleX.NaN; int n = (int)Math.Floor(v + 0.5); double u = v - n; Debug.Assert(u >= -0.5 && u < 0.5); // Ensure u in [-1/2, 1/2) // compute J{v+1}(x) / J{v}(x) var(fv, s) = J_CF1(v, x); var(Jvmn, Jvmnp1, scale) = Recurrence.BackwardJY_B(v, x, n, s, fv * s); double ratio = s / Jvmn; // normalization double fu = Jvmnp1 / Jvmn; // prev/current, can also call CF1_jy() to get fu, not much difference in precision //double fuCF; //int sfu; //Bessel.CF1_jy(u,x,fuCF,sfu); //fu = fuCF; var(p, q) = JY_CF2(u, x); // continued fraction JY_CF2 double t = u / x - fu; // t = J'/J double gamma = (p - t) / q; // // We can't allow gamma to cancel out to zero competely as it messes up // the subsequent logic. So pretend that one bit didn't cancel out // and set to a suitably small value. The only test case we've been able to // find for this, is when v = 8.5 and x = 4*PI. // if (gamma == 0) { gamma = u * DoubleLimits.MachineEpsilon / x; } double W = (2 / Math.PI) / x; // Wronskian double Ju = Math.Sign(Jvmn) * Math.Sqrt(W / (q + gamma * (p - t))); double Jv = Ju * ratio; // normalization J = Math2.Ldexp(Jv, -scale); if (needY) { double Yu = gamma * Ju; double Yu1 = Yu * (u / x - p - q / gamma); var(JYvpn, JYvpnm1, YScale) = Recurrence.ForwardJY_B(u + 1, x, n, Yu1, Yu); Y = DoubleX.Ldexp(JYvpnm1, YScale); } return(J, Y); }
/// <summary> /// Compute K{v}(x) and K{v+1}(x). Approximately O(v). /// <para>Multiply K{v}(x) and K{v+1}(x) by 2^binaryScale to get the true result</para> /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <returns></returns> static (double Kv, double Kvp1, int BScale) K_CF(double v, double x) { int binaryScale = 0; // Ku = K{u}, Ku1 = K{u+1} double Ku, Kup1; int n = (int)Math.Floor(v + 0.5); double u = v - n; // -1/2 <= u < 1/2 // for x in (0, 2], use Temme series // otherwise use scaled continued fraction K_CF2 // to prevent Kv from underflowing too quickly for large x bool expScale = false; if (x <= 2) { (Ku, Kup1) = K_Temme(u, x); } else { expScale = true; (Ku, Kup1) = K_CF2(u, x, expScale); } var(Kvp1, Kv, bScale) = Recurrence.ForwardK_B(u + 1, x, n, Kup1, Ku); if (expScale) { var sf = DoubleX.Ldexp(DoubleX.Exp(-x), bScale); Kv = Kv * sf.Mantissa; Kvp1 = Kvp1 * sf.Mantissa; binaryScale = sf.Exponent; } else { binaryScale = bScale; } return(Kv, Kvp1, binaryScale); }
/// <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 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> /// 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); }
/// <summary> /// Computes J{v}(x), Y{v}(x) using a combination of asymptotic approximation and recurrence /// </summary> /// <param name="v"></param> /// <param name="x"></param> /// <param name="needJ"></param> /// <param name="needY"></param> /// <returns></returns> static (double J, DoubleX Y) JY_AsymRecurrence(double v, double x, bool needJ, bool needY) { Debug.Assert(v >= 0 && x >= 1, "Requires positive values for v,x"); Debug.Assert(v < int.MaxValue, "v too large: v = " + v); var J = double.NaN; var Y = DoubleX.NaN; int nPos = (int)Math.Floor(v); double uPos = v - nPos; // Using Hankel, find: // J{v-Floor(v)}(x) and J{v-Floor(v)+1}(x) // Y{v-Floor(v)}(x) and Y{v-Floor(v)+1}(x) // then use recurrence to find J{v}(x), Y{v}(x) double u0, u1;; int n; if (x >= 9) { // set the start of the recurrence near sqrt(x) double maxV = Math.Floor(Math.Sqrt(x)); u1 = (maxV - 1) + uPos; u0 = u1 - 1; n = (int)Math.Floor(v - u1 + 0.5); Debug.Assert(n >= 0); } else { u0 = uPos; u1 = uPos + 1; n = nPos - 1; } Debug.Assert(x >= HankelAsym.JYMinX(u1), "x is too small for HankelAsym"); var(Ju, Yu) = HankelAsym.JY(u0, x); var(Jup1, Yup1) = HankelAsym.JY(u1, x); if (needJ) { if (v < x) { J = Recurrence.ForwardJY(u1, x, n, Jup1, Ju).JYvpn; } else { // Use fv = J{v+1}(x) / J{v}(x) // and backward recurrence to find (J{v-n+1}/J{v-n}) var(fv, s) = J_CF1(v, x); var(Jvmn, Jvmnp1, scale) = Recurrence.BackwardJY_B(v, x, n, s, fv * s); var Jv = Math2.Ldexp(Jup1 / Jvmn, -scale); J = s * Jv; // normalization } } if (needY) { var(Yv, Yvm1, YScale) = Recurrence.ForwardJY_B(u1, x, n, Yup1, Yu); Y = DoubleX.Ldexp(Yv, YScale); } return(J, Y); }