Chris@16: // Copyright (c) 2006 Xiaogang Zhang Chris@16: // Use, modification and distribution are subject to the Chris@16: // Boost Software License, Version 1.0. (See accompanying file Chris@16: // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) Chris@16: Chris@16: #ifndef BOOST_MATH_BESSEL_IK_HPP Chris@16: #define BOOST_MATH_BESSEL_IK_HPP Chris@16: Chris@16: #ifdef _MSC_VER Chris@16: #pragma once Chris@16: #endif Chris@16: Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: Chris@16: // Modified Bessel functions of the first and second kind of fractional order Chris@16: Chris@16: namespace boost { namespace math { Chris@16: Chris@16: namespace detail { Chris@16: Chris@16: template Chris@16: struct cyl_bessel_i_small_z Chris@16: { Chris@16: typedef T result_type; Chris@16: Chris@16: cyl_bessel_i_small_z(T v_, T z_) : k(0), v(v_), mult(z_*z_/4) Chris@16: { Chris@16: BOOST_MATH_STD_USING Chris@16: term = 1; Chris@16: } Chris@16: Chris@16: T operator()() Chris@16: { Chris@16: T result = term; Chris@16: ++k; Chris@16: term *= mult / k; Chris@16: term /= k + v; Chris@16: return result; Chris@16: } Chris@16: private: Chris@16: unsigned k; Chris@16: T v; Chris@16: T term; Chris@16: T mult; Chris@16: }; Chris@16: Chris@16: template Chris@16: inline T bessel_i_small_z_series(T v, T x, const Policy& pol) Chris@16: { Chris@16: BOOST_MATH_STD_USING Chris@16: T prefix; Chris@16: if(v < max_factorial::value) Chris@16: { Chris@16: prefix = pow(x / 2, v) / boost::math::tgamma(v + 1, pol); Chris@16: } Chris@16: else Chris@16: { Chris@16: prefix = v * log(x / 2) - boost::math::lgamma(v + 1, pol); Chris@16: prefix = exp(prefix); Chris@16: } Chris@16: if(prefix == 0) Chris@16: return prefix; Chris@16: Chris@16: cyl_bessel_i_small_z s(v, x); Chris@16: boost::uintmax_t max_iter = policies::get_max_series_iterations(); Chris@16: #if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) Chris@16: T zero = 0; Chris@16: T result = boost::math::tools::sum_series(s, boost::math::policies::get_epsilon(), max_iter, zero); Chris@16: #else Chris@16: T result = boost::math::tools::sum_series(s, boost::math::policies::get_epsilon(), max_iter); Chris@16: #endif Chris@16: policies::check_series_iterations("boost::math::bessel_j_small_z_series<%1%>(%1%,%1%)", max_iter, pol); Chris@16: return prefix * result; Chris@16: } Chris@16: Chris@16: // Calculate K(v, x) and K(v+1, x) by method analogous to Chris@16: // Temme, Journal of Computational Physics, vol 21, 343 (1976) Chris@16: template Chris@16: int temme_ik(T v, T x, T* K, T* K1, const Policy& pol) Chris@16: { Chris@16: T f, h, p, q, coef, sum, sum1, tolerance; Chris@16: T a, b, c, d, sigma, gamma1, gamma2; Chris@16: unsigned long k; Chris@16: Chris@16: BOOST_MATH_STD_USING Chris@16: using namespace boost::math::tools; Chris@16: using namespace boost::math::constants; Chris@16: Chris@16: Chris@16: // |x| <= 2, Temme series converge rapidly Chris@16: // |x| > 2, the larger the |x|, the slower the convergence Chris@16: BOOST_ASSERT(abs(x) <= 2); Chris@16: BOOST_ASSERT(abs(v) <= 0.5f); Chris@16: Chris@16: T gp = boost::math::tgamma1pm1(v, pol); Chris@16: T gm = boost::math::tgamma1pm1(-v, pol); Chris@16: Chris@16: a = log(x / 2); Chris@16: b = exp(v * a); Chris@16: sigma = -a * v; Chris@16: c = abs(v) < tools::epsilon() ? Chris@16: T(1) : T(boost::math::sin_pi(v) / (v * pi())); Chris@16: d = abs(sigma) < tools::epsilon() ? Chris@16: T(1) : T(sinh(sigma) / sigma); Chris@16: gamma1 = abs(v) < tools::epsilon() ? Chris@16: T(-euler()) : T((0.5f / v) * (gp - gm) * c); Chris@16: gamma2 = (2 + gp + gm) * c / 2; Chris@16: Chris@16: // initial values Chris@16: p = (gp + 1) / (2 * b); Chris@16: q = (1 + gm) * b / 2; Chris@16: f = (cosh(sigma) * gamma1 + d * (-a) * gamma2) / c; Chris@16: h = p; Chris@16: coef = 1; Chris@16: sum = coef * f; Chris@16: sum1 = coef * h; Chris@16: Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(p); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(q); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(f); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(sigma); Chris@16: BOOST_MATH_INSTRUMENT_CODE(sinh(sigma)); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(gamma1); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(gamma2); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(c); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(d); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(a); Chris@16: Chris@16: // series summation Chris@16: tolerance = tools::epsilon(); Chris@16: for (k = 1; k < policies::get_max_series_iterations(); k++) Chris@16: { Chris@16: f = (k * f + p + q) / (k*k - v*v); Chris@16: p /= k - v; Chris@16: q /= k + v; Chris@16: h = p - k * f; Chris@16: coef *= x * x / (4 * k); Chris@16: sum += coef * f; Chris@16: sum1 += coef * h; Chris@16: if (abs(coef * f) < abs(sum) * tolerance) Chris@16: { Chris@16: break; Chris@16: } Chris@16: } Chris@16: policies::check_series_iterations("boost::math::bessel_ik<%1%>(%1%,%1%) in temme_ik", k, pol); Chris@16: Chris@16: *K = sum; Chris@16: *K1 = 2 * sum1 / x; Chris@16: Chris@16: return 0; Chris@16: } Chris@16: Chris@16: // Evaluate continued fraction fv = I_(v+1) / I_v, derived from Chris@16: // Abramowitz and Stegun, Handbook of Mathematical Functions, 1972, 9.1.73 Chris@16: template Chris@16: int CF1_ik(T v, T x, T* fv, const Policy& pol) Chris@16: { Chris@16: T C, D, f, a, b, delta, tiny, tolerance; Chris@16: unsigned long k; Chris@16: Chris@16: BOOST_MATH_STD_USING Chris@16: Chris@16: // |x| <= |v|, CF1_ik converges rapidly Chris@16: // |x| > |v|, CF1_ik needs O(|x|) iterations to converge Chris@16: Chris@16: // modified Lentz's method, see Chris@16: // Lentz, Applied Optics, vol 15, 668 (1976) Chris@16: tolerance = 2 * tools::epsilon(); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(tolerance); Chris@16: tiny = sqrt(tools::min_value()); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(tiny); Chris@16: C = f = tiny; // b0 = 0, replace with tiny Chris@16: D = 0; Chris@16: for (k = 1; k < policies::get_max_series_iterations(); k++) Chris@16: { Chris@16: a = 1; Chris@16: b = 2 * (v + k) / x; Chris@16: C = b + a / C; Chris@16: D = b + a * D; Chris@16: if (C == 0) { C = tiny; } Chris@16: if (D == 0) { D = tiny; } Chris@16: D = 1 / D; Chris@16: delta = C * D; Chris@16: f *= delta; Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(delta-1); Chris@16: if (abs(delta - 1) <= tolerance) Chris@16: { Chris@16: break; Chris@16: } Chris@16: } Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(k); Chris@16: policies::check_series_iterations("boost::math::bessel_ik<%1%>(%1%,%1%) in CF1_ik", k, pol); Chris@16: Chris@16: *fv = f; Chris@16: Chris@16: return 0; Chris@16: } Chris@16: Chris@16: // Calculate K(v, x) and K(v+1, x) by evaluating continued fraction Chris@16: // z1 / z0 = U(v+1.5, 2v+1, 2x) / U(v+0.5, 2v+1, 2x), see Chris@16: // Thompson and Barnett, Computer Physics Communications, vol 47, 245 (1987) Chris@16: template Chris@16: int CF2_ik(T v, T x, T* Kv, T* Kv1, const Policy& pol) Chris@16: { Chris@16: BOOST_MATH_STD_USING Chris@16: using namespace boost::math::constants; Chris@16: Chris@16: T S, C, Q, D, f, a, b, q, delta, tolerance, current, prev; Chris@16: unsigned long k; Chris@16: Chris@16: // |x| >= |v|, CF2_ik converges rapidly Chris@16: // |x| -> 0, CF2_ik fails to converge Chris@16: Chris@16: BOOST_ASSERT(abs(x) > 1); Chris@16: Chris@16: // Steed's algorithm, see Thompson and Barnett, Chris@16: // Journal of Computational Physics, vol 64, 490 (1986) Chris@16: tolerance = tools::epsilon(); Chris@16: a = v * v - 0.25f; Chris@16: b = 2 * (x + 1); // b1 Chris@16: D = 1 / b; // D1 = 1 / b1 Chris@16: f = delta = D; // f1 = delta1 = D1, coincidence Chris@16: prev = 0; // q0 Chris@16: current = 1; // q1 Chris@16: Q = C = -a; // Q1 = C1 because q1 = 1 Chris@16: S = 1 + Q * delta; // S1 Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(tolerance); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(a); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(b); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(D); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(f); Chris@16: Chris@16: for (k = 2; k < policies::get_max_series_iterations(); k++) // starting from 2 Chris@16: { Chris@16: // continued fraction f = z1 / z0 Chris@16: a -= 2 * (k - 1); Chris@16: b += 2; Chris@16: D = 1 / (b + a * D); Chris@16: delta *= b * D - 1; Chris@16: f += delta; Chris@16: Chris@16: // series summation S = 1 + \sum_{n=1}^{\infty} C_n * z_n / z_0 Chris@16: q = (prev - (b - 2) * current) / a; Chris@16: prev = current; Chris@16: current = q; // forward recurrence for q Chris@16: C *= -a / k; Chris@16: Q += C * q; Chris@16: S += Q * delta; Chris@16: // Chris@16: // Under some circumstances q can grow very small and C very Chris@16: // large, leading to under/overflow. This is particularly an Chris@16: // issue for types which have many digits precision but a narrow Chris@16: // exponent range. A typical example being a "double double" type. Chris@16: // To avoid this situation we can normalise q (and related prev/current) Chris@16: // and C. All other variables remain unchanged in value. A typical Chris@16: // test case occurs when x is close to 2, for example cyl_bessel_k(9.125, 2.125). Chris@16: // Chris@16: if(q < tools::epsilon()) Chris@16: { Chris@16: C *= q; Chris@16: prev /= q; Chris@16: current /= q; Chris@16: q = 1; Chris@16: } Chris@16: Chris@16: // S converges slower than f Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(Q * delta); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(abs(S) * tolerance); Chris@101: BOOST_MATH_INSTRUMENT_VARIABLE(S); Chris@16: if (abs(Q * delta) < abs(S) * tolerance) Chris@16: { Chris@16: break; Chris@16: } Chris@16: } Chris@16: policies::check_series_iterations("boost::math::bessel_ik<%1%>(%1%,%1%) in CF2_ik", k, pol); Chris@16: Chris@101: if(x >= tools::log_max_value()) Chris@101: *Kv = exp(0.5f * log(pi() / (2 * x)) - x - log(S)); Chris@101: else Chris@101: *Kv = sqrt(pi() / (2 * x)) * exp(-x) / S; Chris@16: *Kv1 = *Kv * (0.5f + v + x + (v * v - 0.25f) * f) / x; Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(*Kv); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(*Kv1); Chris@16: Chris@16: return 0; Chris@16: } Chris@16: Chris@16: enum{ Chris@16: need_i = 1, Chris@16: need_k = 2 Chris@16: }; Chris@16: Chris@16: // Compute I(v, x) and K(v, x) simultaneously by Temme's method, see Chris@16: // Temme, Journal of Computational Physics, vol 19, 324 (1975) Chris@16: template Chris@16: int bessel_ik(T v, T x, T* I, T* K, int kind, const Policy& pol) Chris@16: { Chris@16: // Kv1 = K_(v+1), fv = I_(v+1) / I_v Chris@16: // Ku1 = K_(u+1), fu = I_(u+1) / I_u Chris@16: T u, Iv, Kv, Kv1, Ku, Ku1, fv; Chris@16: T W, current, prev, next; Chris@16: bool reflect = false; Chris@16: unsigned n, k; Chris@16: int org_kind = kind; Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(v); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(x); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(kind); Chris@16: Chris@16: BOOST_MATH_STD_USING Chris@16: using namespace boost::math::tools; Chris@16: using namespace boost::math::constants; Chris@16: Chris@16: static const char* function = "boost::math::bessel_ik<%1%>(%1%,%1%)"; Chris@16: Chris@16: if (v < 0) Chris@16: { Chris@16: reflect = true; Chris@16: v = -v; // v is non-negative from here Chris@16: kind |= need_k; Chris@16: } Chris@16: n = iround(v, pol); Chris@16: u = v - n; // -1/2 <= u < 1/2 Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(n); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(u); Chris@16: Chris@16: if (x < 0) Chris@16: { Chris@16: *I = *K = policies::raise_domain_error(function, Chris@16: "Got x = %1% but real argument x must be non-negative, complex number result not supported.", x, pol); Chris@16: return 1; Chris@16: } Chris@16: if (x == 0) Chris@16: { Chris@16: Iv = (v == 0) ? static_cast(1) : static_cast(0); Chris@16: if(kind & need_k) Chris@16: { Chris@16: Kv = policies::raise_overflow_error(function, 0, pol); Chris@16: } Chris@16: else Chris@16: { Chris@16: Kv = std::numeric_limits::quiet_NaN(); // any value will do Chris@16: } Chris@16: Chris@16: if(reflect && (kind & need_i)) Chris@16: { Chris@16: T z = (u + n % 2); Chris@16: Iv = boost::math::sin_pi(z, pol) == 0 ? Chris@16: Iv : Chris@16: policies::raise_overflow_error(function, 0, pol); // reflection formula Chris@16: } Chris@16: Chris@16: *I = Iv; Chris@16: *K = Kv; Chris@16: return 0; Chris@16: } Chris@16: Chris@16: // x is positive until reflection Chris@16: W = 1 / x; // Wronskian Chris@16: if (x <= 2) // x in (0, 2] Chris@16: { Chris@16: temme_ik(u, x, &Ku, &Ku1, pol); // Temme series Chris@16: } Chris@16: else // x in (2, \infty) Chris@16: { Chris@16: CF2_ik(u, x, &Ku, &Ku1, pol); // continued fraction CF2_ik Chris@16: } Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(Ku); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(Ku1); Chris@16: prev = Ku; Chris@16: current = Ku1; Chris@16: T scale = 1; Chris@16: for (k = 1; k <= n; k++) // forward recurrence for K Chris@16: { Chris@16: T fact = 2 * (u + k) / x; Chris@16: if((tools::max_value() - fabs(prev)) / fact < fabs(current)) Chris@16: { Chris@16: prev /= current; Chris@16: scale /= current; Chris@16: current = 1; Chris@16: } Chris@16: next = fact * current + prev; Chris@16: prev = current; Chris@16: current = next; Chris@16: } Chris@16: Kv = prev; Chris@16: Kv1 = current; Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(Kv); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(Kv1); Chris@16: if(kind & need_i) Chris@16: { Chris@16: T lim = (4 * v * v + 10) / (8 * x); Chris@16: lim *= lim; Chris@16: lim *= lim; Chris@16: lim /= 24; Chris@16: if((lim < tools::epsilon() * 10) && (x > 100)) Chris@16: { Chris@16: // x is huge compared to v, CF1 may be very slow Chris@16: // to converge so use asymptotic expansion for large Chris@16: // x case instead. Note that the asymptotic expansion Chris@16: // isn't very accurate - so it's deliberately very hard Chris@16: // to get here - probably we're going to overflow: Chris@16: Iv = asymptotic_bessel_i_large_x(v, x, pol); Chris@16: } Chris@16: else if((v > 0) && (x / v < 0.25)) Chris@16: { Chris@16: Iv = bessel_i_small_z_series(v, x, pol); Chris@16: } Chris@16: else Chris@16: { Chris@16: CF1_ik(v, x, &fv, pol); // continued fraction CF1_ik Chris@16: Iv = scale * W / (Kv * fv + Kv1); // Wronskian relation Chris@16: } Chris@16: } Chris@16: else Chris@16: Iv = std::numeric_limits::quiet_NaN(); // any value will do Chris@16: Chris@16: if (reflect) Chris@16: { Chris@16: T z = (u + n % 2); Chris@16: T fact = (2 / pi()) * (boost::math::sin_pi(z) * Kv); Chris@16: if(fact == 0) Chris@16: *I = Iv; Chris@16: else if(tools::max_value() * scale < fact) Chris@16: *I = (org_kind & need_i) ? T(sign(fact) * sign(scale) * policies::raise_overflow_error(function, 0, pol)) : T(0); Chris@16: else Chris@16: *I = Iv + fact / scale; // reflection formula Chris@16: } Chris@16: else Chris@16: { Chris@16: *I = Iv; Chris@16: } Chris@16: if(tools::max_value() * scale < Kv) Chris@16: *K = (org_kind & need_k) ? T(sign(Kv) * sign(scale) * policies::raise_overflow_error(function, 0, pol)) : T(0); Chris@16: else Chris@16: *K = Kv / scale; Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(*I); Chris@16: BOOST_MATH_INSTRUMENT_VARIABLE(*K); Chris@16: return 0; Chris@16: } Chris@16: Chris@16: }}} // namespaces Chris@16: Chris@16: #endif // BOOST_MATH_BESSEL_IK_HPP Chris@16: