123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- ///////////////////////////////////////////////////////////////////////////////
- // Copyright 2018 John Maddock
- // Distributed under the Boost
- // Software License, Version 1.0. (See accompanying file
- // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
- #ifndef BOOST_HYPERGEOMETRIC_1F1_LARGE_ABZ_HPP_
- #define BOOST_HYPERGEOMETRIC_1F1_LARGE_ABZ_HPP_
- #include <boost/math/special_functions/detail/hypergeometric_1F1_bessel.hpp>
- #include <boost/math/special_functions/detail/hypergeometric_series.hpp>
- #include <boost/math/special_functions/gamma.hpp>
- #include <boost/math/special_functions/trunc.hpp>
- namespace boost { namespace math { namespace detail {
- template <class T>
- inline bool is_negative_integer(const T& x)
- {
- using std::floor;
- return (x <= 0) && (floor(x) == x);
- }
- template <class T, class Policy>
- struct hypergeometric_1F1_igamma_series
- {
- enum{ cache_size = 64 };
- typedef T result_type;
- hypergeometric_1F1_igamma_series(const T& alpha, const T& delta, const T& x, const Policy& pol)
- : delta_poch(-delta), alpha_poch(alpha), x(x), k(0), cache_offset(0), pol(pol)
- {
- BOOST_MATH_STD_USING
- T log_term = log(x) * -alpha;
- log_scaling = lltrunc(log_term - 3 - boost::math::tools::log_min_value<T>() / 50);
- term = exp(log_term - log_scaling);
- refill_cache();
- }
- T operator()()
- {
- if (k - cache_offset >= cache_size)
- {
- cache_offset += cache_size;
- refill_cache();
- }
- T result = term * gamma_cache[k - cache_offset];
- term *= delta_poch * alpha_poch / (++k * x);
- delta_poch += 1;
- alpha_poch += 1;
- return result;
- }
- void refill_cache()
- {
- typedef typename lanczos::lanczos<T, Policy>::type lanczos_type;
- gamma_cache[cache_size - 1] = boost::math::gamma_p(alpha_poch + ((int)cache_size - 1), x, pol);
- for (int i = cache_size - 1; i > 0; --i)
- {
- gamma_cache[i - 1] = gamma_cache[i] >= 1 ? T(1) : T(gamma_cache[i] + regularised_gamma_prefix(T(alpha_poch + (i - 1)), x, pol, lanczos_type()) / (alpha_poch + (i - 1)));
- }
- }
- T delta_poch, alpha_poch, x, term;
- T gamma_cache[cache_size];
- int k;
- long long log_scaling;
- int cache_offset;
- Policy pol;
- };
- template <class T, class Policy>
- T hypergeometric_1F1_igamma(const T& a, const T& b, const T& x, const T& b_minus_a, const Policy& pol, long long& log_scaling)
- {
- BOOST_MATH_STD_USING
- if (b_minus_a == 0)
- {
- // special case: M(a,a,z) == exp(z)
- long long scale = lltrunc(x, pol);
- log_scaling += scale;
- return exp(x - scale);
- }
- hypergeometric_1F1_igamma_series<T, Policy> s(b_minus_a, a - 1, x, pol);
- log_scaling += s.log_scaling;
- std::uintmax_t max_iter = boost::math::policies::get_max_series_iterations<Policy>();
- T result = boost::math::tools::sum_series(s, boost::math::policies::get_epsilon<T, Policy>(), max_iter);
- boost::math::policies::check_series_iterations<T>("boost::math::tgamma<%1%>(%1%,%1%)", max_iter, pol);
- T log_prefix = x + boost::math::lgamma(b, pol) - boost::math::lgamma(a, pol);
- long long scale = lltrunc(log_prefix);
- log_scaling += scale;
- return result * exp(log_prefix - scale);
- }
- template <class T, class Policy>
- T hypergeometric_1F1_shift_on_a(T h, const T& a_local, const T& b_local, const T& x, int a_shift, const Policy& pol, long long& log_scaling)
- {
- BOOST_MATH_STD_USING
- T a = a_local + a_shift;
- if (a_shift == 0)
- return h;
- else if (a_shift > 0)
- {
- //
- // Forward recursion on a is stable as long as 2a-b+z > 0.
- // If 2a-b+z < 0 then backwards recursion is stable even though
- // the function may be strictly increasing with a. Potentially
- // we may need to split the recurrence in 2 sections - one using
- // forward recursion, and one backwards.
- //
- // We will get the next seed value from the ratio
- // on b as that's stable and quick to compute.
- //
- T crossover_a = (b_local - x) / 2;
- int crossover_shift = itrunc(crossover_a - a_local);
- if (crossover_shift > 1)
- {
- //
- // Forwards recursion will start off unstable, but may switch to the stable direction later.
- // Start in the middle and go in both directions:
- //
- if (crossover_shift > a_shift)
- crossover_shift = a_shift;
- crossover_a = a_local + crossover_shift;
- boost::math::detail::hypergeometric_1F1_recurrence_b_coefficients<T> b_coef(crossover_a, b_local, x);
- std::uintmax_t max_iter = boost::math::policies::get_max_series_iterations<Policy>();
- T b_ratio = boost::math::tools::function_ratio_from_backwards_recurrence(b_coef, boost::math::policies::get_epsilon<T, Policy>(), max_iter);
- boost::math::policies::check_series_iterations<T>("boost::math::hypergeometric_1F1_large_abz<%1%>(%1%,%1%,%1%)", max_iter, pol);
- //
- // Convert to a ratio:
- // (1+a-b)M(a, b, z) - aM(a+1, b, z) + (b-1)M(a, b-1, z) = 0
- //
- // hence: M(a+1,b,z) = ((1+a-b) / a) M(a,b,z) + ((b-1) / a) M(a,b,z)/b_ratio
- //
- T first = 1;
- T second = ((1 + crossover_a - b_local) / crossover_a) + ((b_local - 1) / crossover_a) / b_ratio;
- //
- // Recurse down to a_local, compare values and re-normalise first and second:
- //
- boost::math::detail::hypergeometric_1F1_recurrence_a_coefficients<T> a_coef(crossover_a, b_local, x);
- long long backwards_scale = 0;
- T comparitor = boost::math::tools::apply_recurrence_relation_backward(a_coef, crossover_shift, second, first, &backwards_scale);
- log_scaling -= backwards_scale;
- if ((h < 1) && (tools::max_value<T>() * h > comparitor))
- {
- // Need to rescale!
- long long scale = lltrunc(log(h), pol) + 1;
- h *= exp(T(-scale));
- log_scaling += scale;
- }
- comparitor /= h;
- first /= comparitor;
- second /= comparitor;
- //
- // Now we can recurse forwards for the rest of the range:
- //
- if (crossover_shift < a_shift)
- {
- boost::math::detail::hypergeometric_1F1_recurrence_a_coefficients<T> a_coef_2(crossover_a + 1, b_local, x);
- h = boost::math::tools::apply_recurrence_relation_forward(a_coef_2, a_shift - crossover_shift - 1, first, second, &log_scaling);
- }
- else
- h = first;
- }
- else
- {
- //
- // Regular case where forwards iteration is stable right from the start:
- //
- boost::math::detail::hypergeometric_1F1_recurrence_b_coefficients<T> b_coef(a_local, b_local, x);
- std::uintmax_t max_iter = boost::math::policies::get_max_series_iterations<Policy>();
- T b_ratio = boost::math::tools::function_ratio_from_backwards_recurrence(b_coef, boost::math::policies::get_epsilon<T, Policy>(), max_iter);
- boost::math::policies::check_series_iterations<T>("boost::math::hypergeometric_1F1_large_abz<%1%>(%1%,%1%,%1%)", max_iter, pol);
- //
- // Convert to a ratio:
- // (1+a-b)M(a, b, z) - aM(a+1, b, z) + (b-1)M(a, b-1, z) = 0
- //
- // hence: M(a+1,b,z) = ((1+a-b) / a) M(a,b,z) + ((b-1) / a) M(a,b,z)/b_ratio
- //
- T second = ((1 + a_local - b_local) / a_local) * h + ((b_local - 1) / a_local) * h / b_ratio;
- boost::math::detail::hypergeometric_1F1_recurrence_a_coefficients<T> a_coef(a_local + 1, b_local, x);
- h = boost::math::tools::apply_recurrence_relation_forward(a_coef, --a_shift, h, second, &log_scaling);
- }
- }
- else
- {
- //
- // We've calculated h for a larger value of a than we want, and need to recurse down.
- // However, only forward iteration is stable, so calculate the ratio, compare values,
- // and normalise. Note that we calculate the ratio on b and convert to a since the
- // direction is the minimal solution for N->+INF.
- //
- // IMPORTANT: this is only currently called for a > b and therefore forwards iteration
- // is the only stable direction as we will only iterate down until a ~ b, but we
- // will check this with an assert:
- //
- BOOST_MATH_ASSERT(2 * a - b_local + x > 0);
- boost::math::detail::hypergeometric_1F1_recurrence_b_coefficients<T> b_coef(a, b_local, x);
- std::uintmax_t max_iter = boost::math::policies::get_max_series_iterations<Policy>();
- T b_ratio = boost::math::tools::function_ratio_from_backwards_recurrence(b_coef, boost::math::policies::get_epsilon<T, Policy>(), max_iter);
- boost::math::policies::check_series_iterations<T>("boost::math::hypergeometric_1F1_large_abz<%1%>(%1%,%1%,%1%)", max_iter, pol);
- //
- // Convert to a ratio:
- // (1+a-b)M(a, b, z) - aM(a+1, b, z) + (b-1)M(a, b-1, z) = 0
- //
- // hence: M(a+1,b,z) = (1+a-b) / a M(a,b,z) + (b-1) / a M(a,b,z)/ (M(a,b,z)/M(a,b-1,z))
- //
- T first = 1; // arbitrary value;
- T second = ((1 + a - b_local) / a) + ((b_local - 1) / a) * (1 / b_ratio);
- if (a_shift == -1)
- h = h / second;
- else
- {
- boost::math::detail::hypergeometric_1F1_recurrence_a_coefficients<T> a_coef(a + 1, b_local, x);
- T comparitor = boost::math::tools::apply_recurrence_relation_forward(a_coef, -(a_shift + 1), first, second);
- if (boost::math::tools::min_value<T>() * comparitor > h)
- {
- // Ooops, need to rescale h:
- long long rescale = lltrunc(log(fabs(h)));
- T scale = exp(T(-rescale));
- h *= scale;
- log_scaling += rescale;
- }
- h = h / comparitor;
- }
- }
- return h;
- }
- template <class T, class Policy>
- T hypergeometric_1F1_shift_on_b(T h, const T& a, const T& b_local, const T& x, int b_shift, const Policy& pol, long long& log_scaling)
- {
- BOOST_MATH_STD_USING
- T b = b_local + b_shift;
- if (b_shift == 0)
- return h;
- else if (b_shift > 0)
- {
- //
- // We get here for b_shift > 0 when b > z. We can't use forward recursion on b - it's unstable,
- // so grab the ratio and work backwards to b - b_shift and normalise.
- //
- boost::math::detail::hypergeometric_1F1_recurrence_b_coefficients<T> b_coef(a, b, x);
- std::uintmax_t max_iter = boost::math::policies::get_max_series_iterations<Policy>();
- T first = 1; // arbitrary value;
- T second = 1 / boost::math::tools::function_ratio_from_backwards_recurrence(b_coef, boost::math::policies::get_epsilon<T, Policy>(), max_iter);
- boost::math::policies::check_series_iterations<T>("boost::math::hypergeometric_1F1_large_abz<%1%>(%1%,%1%,%1%)", max_iter, pol);
- if (b_shift == 1)
- h = h / second;
- else
- {
- //
- // Reset coefficients and recurse:
- //
- boost::math::detail::hypergeometric_1F1_recurrence_b_coefficients<T> b_coef_2(a, b - 1, x);
- long long local_scale = 0;
- T comparitor = boost::math::tools::apply_recurrence_relation_backward(b_coef_2, --b_shift, first, second, &local_scale);
- log_scaling -= local_scale;
- if (boost::math::tools::min_value<T>() * comparitor > h)
- {
- // Ooops, need to rescale h:
- long long rescale = lltrunc(log(fabs(h)));
- T scale = exp(T(-rescale));
- h *= scale;
- log_scaling += rescale;
- }
- h = h / comparitor;
- }
- }
- else
- {
- T second;
- if (a == b_local)
- {
- // recurrence is trivial for a == b and method of ratios fails as the c-term goes to zero:
- second = -b_local * (1 - b_local - x) * h / (b_local * (b_local - 1));
- }
- else
- {
- BOOST_MATH_ASSERT(!is_negative_integer(b - a));
- boost::math::detail::hypergeometric_1F1_recurrence_b_coefficients<T> b_coef(a, b_local, x);
- std::uintmax_t max_iter = boost::math::policies::get_max_series_iterations<Policy>();
- second = h / boost::math::tools::function_ratio_from_backwards_recurrence(b_coef, boost::math::policies::get_epsilon<T, Policy>(), max_iter);
- boost::math::policies::check_series_iterations<T>("boost::math::hypergeometric_1F1_large_abz<%1%>(%1%,%1%,%1%)", max_iter, pol);
- }
- if (b_shift == -1)
- h = second;
- else
- {
- boost::math::detail::hypergeometric_1F1_recurrence_b_coefficients<T> b_coef_2(a, b_local - 1, x);
- h = boost::math::tools::apply_recurrence_relation_backward(b_coef_2, -(++b_shift), h, second, &log_scaling);
- }
- }
- return h;
- }
- template <class T, class Policy>
- T hypergeometric_1F1_large_igamma(const T& a, const T& b, const T& x, const T& b_minus_a, const Policy& pol, long long& log_scaling)
- {
- BOOST_MATH_STD_USING
- //
- // We need a < b < z in order to ensure there's at least a chance of convergence,
- // we can use recurrence relations to shift forwards on a+b or just a to achieve this,
- // for decent accuracy, try to keep 2b - 1 < a < 2b < z
- //
- int b_shift = b * 2 < x ? 0 : itrunc(b - x / 2);
- int a_shift = a > b - b_shift ? -itrunc(b - b_shift - a - 1) : -itrunc(b - b_shift - a);
- if (a_shift < 0)
- {
- // Might as well do all the shifting on b as scale a downwards:
- b_shift -= a_shift;
- a_shift = 0;
- }
- T a_local = a - a_shift;
- T b_local = b - b_shift;
- T b_minus_a_local = (a_shift == 0) && (b_shift == 0) ? b_minus_a : b_local - a_local;
- long long local_scaling = 0;
- T h = hypergeometric_1F1_igamma(a_local, b_local, x, b_minus_a_local, pol, local_scaling);
- log_scaling += local_scaling;
- //
- // Apply shifts on a and b as required:
- //
- h = hypergeometric_1F1_shift_on_a(h, a_local, b_local, x, a_shift, pol, log_scaling);
- h = hypergeometric_1F1_shift_on_b(h, a, b_local, x, b_shift, pol, log_scaling);
- return h;
- }
- template <class T, class Policy>
- T hypergeometric_1F1_large_series(const T& a, const T& b, const T& z, const Policy& pol, long long& log_scaling)
- {
- BOOST_MATH_STD_USING
- //
- // We make a small, and b > z:
- //
- int a_shift(0), b_shift(0);
- if (a * z > b)
- {
- a_shift = itrunc(a) - 5;
- b_shift = b < z ? itrunc(b - z - 1) : 0;
- }
- //
- // If a_shift is trivially small, there's really not much point in losing
- // accuracy to save a couple of iterations:
- //
- if (a_shift < 5)
- a_shift = 0;
- T a_local = a - a_shift;
- T b_local = b - b_shift;
- T h = boost::math::detail::hypergeometric_1F1_generic_series(a_local, b_local, z, pol, log_scaling, "hypergeometric_1F1_large_series<%1%>(a,b,z)");
- //
- // Apply shifts on a and b as required:
- //
- if (a_shift && (a_local == 0))
- {
- //
- // Shifting on a via method of ratios in hypergeometric_1F1_shift_on_a fails when
- // a_local == 0. However, the value of h calculated was trivial (unity), so
- // calculate a second 1F1 for a == 1 and recurse as normal:
- //
- long long scale = 0;
- T h2 = boost::math::detail::hypergeometric_1F1_generic_series(T(a_local + 1), b_local, z, pol, scale, "hypergeometric_1F1_large_series<%1%>(a,b,z)");
- if (scale != log_scaling)
- {
- h2 *= exp(T(scale - log_scaling));
- }
- boost::math::detail::hypergeometric_1F1_recurrence_a_coefficients<T> coef(a_local + 1, b_local, z);
- h = boost::math::tools::apply_recurrence_relation_forward(coef, a_shift - 1, h, h2, &log_scaling);
- h = hypergeometric_1F1_shift_on_b(h, a, b_local, z, b_shift, pol, log_scaling);
- }
- else
- {
- h = hypergeometric_1F1_shift_on_a(h, a_local, b_local, z, a_shift, pol, log_scaling);
- h = hypergeometric_1F1_shift_on_b(h, a, b_local, z, b_shift, pol, log_scaling);
- }
- return h;
- }
- template <class T, class Policy>
- T hypergeometric_1F1_large_13_3_6_series(const T& a, const T& b, const T& z, const Policy& pol, long long& log_scaling)
- {
- BOOST_MATH_STD_USING
- //
- // A&S 13.3.6 is good only when a ~ b, but isn't too fussy on the size of z.
- // So shift b to match a (b shifting seems to be more stable via method of ratios).
- //
- int b_shift = itrunc(b - a);
- if ((b_shift < 0) && (b - b_shift != a))
- b_shift -= 1;
- T b_local = b - b_shift;
- if ((b_local - a - 0.5 <= 0) && (b_local != a))
- {
- // Make sure b_local - a - 0.5 > 0
- b_shift -= 1;
- b_local += 1;
- }
- T h = boost::math::detail::hypergeometric_1F1_AS_13_3_6(a, b_local, z, T(b_local - a), pol, log_scaling);
- return hypergeometric_1F1_shift_on_b(h, a, b_local, z, b_shift, pol, log_scaling);
- }
- template <class T, class Policy>
- T hypergeometric_1F1_large_abz(const T& a, const T& b, const T& z, const Policy& pol, long long& log_scaling)
- {
- BOOST_MATH_STD_USING
- //
- // This is the selection logic to pick the "best" method.
- // We have a,b,z >> 0 and need to compute the approximate cost of each method
- // and then select whichever wins out.
- //
- enum method
- {
- method_series = 0,
- method_shifted_series,
- method_gamma,
- method_bessel
- };
- //
- // Cost of direct series, is the approx number of terms required until we hit convergence:
- //
- T current_cost = (sqrt(16 * z * (3 * a + z) + 9 * b * b - 24 * b * z) - 3 * b + 4 * z) / 6;
- method current_method = method_series;
- //
- // Cost of shifted series, is the number of recurrences required to move to a zone where
- // the series is convergent right from the start.
- // Note that recurrence relations fail for very small b, and too many recurrences on a
- // will completely destroy all our digits.
- // Also note that the method fails when b-a is a negative integer unless b is already
- // larger than z and thus does not need shifting.
- //
- T cost = a + ((b < z) ? T(z - b) : T(0));
- if((b > 1) && (cost < current_cost) && ((b > z) || !is_negative_integer(b-a)))
- {
- current_method = method_shifted_series;
- current_cost = cost;
- }
- //
- // Cost for gamma function method is the number of recurrences required to move it
- // into a convergent zone, note that recurrence relations fail for very small b.
- // Also add on a fudge factor to account for the fact that this method is both
- // more expensive to compute (requires gamma functions), and less accurate than the
- // methods above:
- //
- T b_shift = fabs(b * 2 < z ? T(0) : T(b - z / 2));
- T a_shift = fabs(a > b - b_shift ? T(-(b - b_shift - a - 1)) : T(-(b - b_shift - a)));
- cost = 1000 + b_shift + a_shift;
- if((b > 1) && (cost <= current_cost))
- {
- current_method = method_gamma;
- current_cost = cost;
- }
- //
- // Cost for bessel approximation is the number of recurrences required to make a ~ b,
- // Note that recurrence relations fail for very small b. We also have issue with large
- // z: either overflow/numeric instability or else the series goes divergent. We seem to be
- // OK for z smaller than log_max_value<Quad> though, maybe we can stretch a little further
- // but that's not clear...
- // Also need to add on a fudge factor to the cost to account for the fact that we need
- // to calculate the Bessel functions, this is not quite as high as the gamma function
- // method above as this is generally more accurate and so preferred if the methods are close:
- //
- cost = 50 + fabs(b - a);
- if((b > 1) && (cost <= current_cost) && (z < tools::log_max_value<T>()) && (z < 11356) && (b - a != 0.5f))
- {
- current_method = method_bessel;
- current_cost = cost;
- }
- switch (current_method)
- {
- case method_series:
- return detail::hypergeometric_1F1_generic_series(a, b, z, pol, log_scaling, "hypergeometric_1f1_large_abz<%1%>(a,b,z)");
- case method_shifted_series:
- return detail::hypergeometric_1F1_large_series(a, b, z, pol, log_scaling);
- case method_gamma:
- return detail::hypergeometric_1F1_large_igamma(a, b, z, T(b - a), pol, log_scaling);
- case method_bessel:
- return detail::hypergeometric_1F1_large_13_3_6_series(a, b, z, pol, log_scaling);
- }
- return 0; // We don't get here!
- }
- } } } // namespaces
- #endif // BOOST_HYPERGEOMETRIC_1F1_LARGE_ABZ_HPP_
|