// Copyright (c) 2006 Xiaogang Zhang // Copyright (c) 2006 John Maddock // Use, modification and distribution are subject to 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) // // History: // XZ wrote the original of this file as part of the Google // Summer of Code 2006. JM modified it to fit into the // Boost.Math conceptual framework better, and to correctly // handle the various corner cases. // #ifndef BOOST_MATH_ELLINT_3_HPP #define BOOST_MATH_ELLINT_3_HPP #include #include #include #include #include #include #include #include // Elliptic integrals (complete and incomplete) of the third kind // Carlson, Numerische Mathematik, vol 33, 1 (1979) namespace boost { namespace math { template typename tools::promote_args::type ellint_3(T1 k, T2 v, T3 phi, const Policy& pol); namespace detail{ template T ellint_pi_imp(T v, T k, T vc, const Policy& pol); // Elliptic integral (Legendre form) of the third kind template T ellint_pi_imp(T v, T phi, T k, T vc, const Policy& pol) { // Note vc = 1-v presumably without cancellation error. T value, x, y, z, p, t; BOOST_MATH_STD_USING using namespace boost::math::tools; using namespace boost::math::constants; static const char* function = "boost::math::ellint_3<%1%>(%1%,%1%,%1%)"; if (abs(k) > 1) { return policies::raise_domain_error(function, "Got k = %1%, function requires |k| <= 1", k, pol); } T sphi = sin(fabs(phi)); if(v > 1 / (sphi * sphi)) { // Complex result is a domain error: return policies::raise_domain_error(function, "Got v = %1%, but result is complex for v > 1 / sin^2(phi)", v, pol); } // Special cases first: if(v == 0) { // A&S 17.7.18 & 19 return (k == 0) ? phi : ellint_f_imp(phi, k, pol); } if(phi == constants::pi() / 2) { // Have to filter this case out before the next // special case, otherwise we might get an infinity from // tan(phi). // Also note that since we can't represent PI/2 exactly // in a T, this is a bit of a guess as to the users true // intent... // return ellint_pi_imp(v, k, vc, pol); } if(k == 0) { // A&S 17.7.20: if(v < 1) { T vcr = sqrt(vc); return atan(vcr * tan(phi)) / vcr; } else if(v == 1) { return tan(phi); } else { // v > 1: T vcr = sqrt(-vc); T arg = vcr * tan(phi); return (boost::math::log1p(arg, pol) - boost::math::log1p(-arg, pol)) / (2 * vcr); } } if(v < 0) { // // If we don't shift to 0 <= v <= 1 we get // cancellation errors later on. Use // A&S 17.7.15/16 to shift to v > 0: // T k2 = k * k; T N = (k2 - v) / (1 - v); T Nm1 = (1 - k2) / (1 - v); T p2 = sqrt(-v * (k2 - v) / (1 - v)); T delta = sqrt(1 - k2 * sphi * sphi); T result = ellint_pi_imp(N, phi, k, Nm1, pol); result *= sqrt(Nm1 * (1 - k2 / N)); result += ellint_f_imp(phi, k, pol) * k2 / p2; result += atan((p2/2) * sin(2 * phi) / delta); result /= sqrt((1 - v) * (1 - k2 / v)); return result; } #if 0 // disabled but retained for future reference: see below. if(v > 1) { // // If v > 1 we can use the identity in A&S 17.7.7/8 // to shift to 0 <= v <= 1. Unfortunately this // identity appears only to function correctly when // 0 <= phi <= pi/2, but it's when phi is outside that // range that we really need it: That's when // Carlson's formula fails, and what's more the periodicity // reduction used below on phi doesn't work when v > 1. // // So we're stuck... the code is archived here in case // some bright spart can figure out the fix. // T k2 = k * k; T N = k2 / v; T Nm1 = (v - k2) / v; T p1 = sqrt((-vc) * (1 - k2 / v)); T delta = sqrt(1 - k2 * sphi * sphi); // // These next two terms have a large amount of cancellation // so it's not clear if this relation is useable even if // the issues with phi > pi/2 can be fixed: // T result = -ellint_pi_imp(N, phi, k, Nm1); result += ellint_f_imp(phi, k); // // This log term gives the complex result when // n > 1/sin^2(phi) // However that case is dealt with as an error above, // so we should always get a real result here: // result += log((delta + p1 * tan(phi)) / (delta - p1 * tan(phi))) / (2 * p1); return result; } #endif // Carlson's algorithm works only for |phi| <= pi/2, // use the integrand's periodicity to normalize phi // // Xiaogang's original code used a cast to long long here // but that fails if T has more digits than a long long, // so rewritten to use fmod instead: // if(fabs(phi) > 1 / tools::epsilon()) { if(v > 1) return policies::raise_domain_error( function, "Got v = %1%, but this is only supported for 0 <= phi <= pi/2", v, pol); // // Phi is so large that phi%pi is necessarily zero (or garbage), // just return the second part of the duplication formula: // value = 2 * fabs(phi) * ellint_pi_imp(v, k, vc, pol) / constants::pi(); } else { T rphi = boost::math::tools::fmod_workaround(fabs(phi), constants::pi() / 2); T m = 2 * (fabs(phi) - rphi) / constants::pi(); int sign = 1; if(boost::math::tools::fmod_workaround(m, T(2)) > 0.5) { m += 1; sign = -1; rphi = constants::pi() / 2 - rphi; } if((m > 0) && (v > 1)) { // // The region with v > 1 and phi outside [0, pi/2] is // currently unsupported: // return policies::raise_domain_error( function, "Got v = %1%, but this is only supported for 0 <= phi <= pi/2", v, pol); } T sinp = sin(rphi); T cosp = cos(rphi); x = cosp * cosp; t = sinp * sinp; y = 1 - k * k * t; z = 1; if(v * t < 0.5) p = 1 - v * t; else p = x + vc * t; value = sign * sinp * (ellint_rf_imp(x, y, z, pol) + v * t * ellint_rj_imp(x, y, z, p, pol) / 3); if(m > 0) value += m * ellint_pi_imp(v, k, vc, pol); } if (phi < 0) { value = -value; // odd function } return value; } // Complete elliptic integral (Legendre form) of the third kind template T ellint_pi_imp(T v, T k, T vc, const Policy& pol) { // Note arg vc = 1-v, possibly without cancellation errors BOOST_MATH_STD_USING using namespace boost::math::tools; static const char* function = "boost::math::ellint_pi<%1%>(%1%,%1%)"; if (abs(k) >= 1) { return policies::raise_domain_error(function, "Got k = %1%, function requires |k| <= 1", k, pol); } if(vc <= 0) { // Result is complex: return policies::raise_domain_error(function, "Got v = %1%, function requires v < 1", v, pol); } if(v == 0) { return (k == 0) ? boost::math::constants::pi() / 2 : ellint_k_imp(k, pol); } if(v < 0) { T k2 = k * k; T N = (k2 - v) / (1 - v); T Nm1 = (1 - k2) / (1 - v); T p2 = sqrt(-v * (k2 - v) / (1 - v)); T result = boost::math::detail::ellint_pi_imp(N, k, Nm1, pol); result *= sqrt(Nm1 * (1 - k2 / N)); result += ellint_k_imp(k, pol) * k2 / p2; result /= sqrt((1 - v) * (1 - k2 / v)); return result; } T x = 0; T y = 1 - k * k; T z = 1; T p = vc; T value = ellint_rf_imp(x, y, z, pol) + v * ellint_rj_imp(x, y, z, p, pol) / 3; return value; } template inline typename tools::promote_args::type ellint_3(T1 k, T2 v, T3 phi, const mpl::false_&) { return boost::math::ellint_3(k, v, phi, policies::policy<>()); } template inline typename tools::promote_args::type ellint_3(T1 k, T2 v, const Policy& pol, const mpl::true_&) { typedef typename tools::promote_args::type result_type; typedef typename policies::evaluation::type value_type; return policies::checked_narrowing_cast( detail::ellint_pi_imp( static_cast(v), static_cast(k), static_cast(1-v), pol), "boost::math::ellint_3<%1%>(%1%,%1%)"); } } // namespace detail template inline typename tools::promote_args::type ellint_3(T1 k, T2 v, T3 phi, const Policy& pol) { typedef typename tools::promote_args::type result_type; typedef typename policies::evaluation::type value_type; return policies::checked_narrowing_cast( detail::ellint_pi_imp( static_cast(v), static_cast(phi), static_cast(k), static_cast(1-v), pol), "boost::math::ellint_3<%1%>(%1%,%1%,%1%)"); } template inline typename tools::promote_args::type ellint_3(T1 k, T2 v, T3 phi) { typedef typename policies::is_policy::type tag_type; return detail::ellint_3(k, v, phi, tag_type()); } template inline typename tools::promote_args::type ellint_3(T1 k, T2 v) { return ellint_3(k, v, policies::policy<>()); } }} // namespaces #endif // BOOST_MATH_ELLINT_3_HPP