// Copyright 2024 Matt Borland // Copyright 2002 - 2011, 2024 Christopher Kormanyos // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt #ifndef BOOST_DECIMAL_DETAIL_CMATH_IMPL_ELLINT_IMPL_HPP #define BOOST_DECIMAL_DETAIL_CMATH_IMPL_ELLINT_IMPL_HPP #include #include #ifndef BOOST_DECIMAL_BUILD_MODULE #include #include #include #endif namespace boost { namespace decimal { namespace detail { namespace ellint_detail { namespace elliptic_series { template constexpr auto agm(T phi, T m2, T& Fpm, T& Km, T* pEm, T* pEpm) noexcept BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, T, void) { // See Chapter/Section 19.8(i) Elliptic Integrals: Quadratic Transformations: // Arithmetic Geometric Mean (AGM), pp. 492-493 of: // F.W.J. Olver et al., NIST Handbook of Mathematical Functions, // Cambridge University Press. The same can also be found // online at https://dlmf.nist.gov/19.8 // In particular, use the AGM algorithm implemented in e_float: // C.M. Kormanyos, "Algorithm 910: A Portable C++ Multiple-Precision // System for Special-Function Calculations", ACM TOMS (37) 4, February 2011. // See also the AGM algorithm as described in "Computation of Special Functions", // Zhang & Jin, 18.3.2, pages 663-665. The implementation is based on the // sample code therein. However, the Mathematica argument convention with // (k^2 --> m) is used, as described in Stephen Wolfram's Mathematica Book, // 4th Ed., Ch. 3.2.11, Page 773. // Make use of the following properties: // F(m | phi + pi*j) = F(m) + 2j * K(m) // E(m | phi + pi*j) = E(m) + 2j * E(m) // as well as (reflection of phi): // F(m, -phi) = -F(m, phi) // E(m, -phi) = -E(m, phi) // The calculations which are needed for EllipticE(...) are only performed if // the results from these will actually be used, in other words only if non-zero // pointers pEm or pEpm have been supplied to this subroutine. // Note that there is special handling for the angular argument phi if this // argument is equal to pi/2. constexpr T my_pi_half { numbers::pi_v / 2 }; const bool phi_is_pi_half { phi == my_pi_half }; constexpr T one { 1 }; const bool has_e { ((pEm != nullptr) || (pEpm != nullptr)) }; if(m2 == one) { Km = std::numeric_limits::quiet_NaN(); const T sp { sin(phi) }; Fpm = phi_is_pi_half ? std::numeric_limits::quiet_NaN() : log((one + sp) / (one - sp)) / 2; if(has_e) { if(pEm != nullptr) { *pEm = one; } if(pEpm != nullptr) { *pEpm = phi_is_pi_half ? one : sp; } } } else { constexpr T zero { 0 }; constexpr T half { 5 , -1 }; T a0 { one }; T b0 { sqrt(one - m2) }; T phi_n { phi }; std::uint32_t p2 { UINT32_C(1) }; T an { }; T cn_2ncn_inner_prod = (has_e ? m2 / 2 : zero); T sin_phi_n_cn_inner_prod = zero; const T break_check { b0 * T { 1, -1 - (std::numeric_limits::digits / 2) } }; for(int n = 0; n < std::numeric_limits::digits; ++n) { an = (a0 + b0) / 2; if(!phi_is_pi_half) { const T atan_arg { (b0 * tan(phi_n)) / a0 }; phi_n += atan(atan_arg); } const T cn_term = (a0 - b0) / 2; if(has_e) { cn_2ncn_inner_prod += ((cn_term * cn_term) * p2); if(pEpm != nullptr) { const T spn_term = ((!phi_is_pi_half) ? sin(phi_n) : zero); sin_phi_n_cn_inner_prod += (cn_term * spn_term); } } p2 = p2 << 1U; if(fabs(cn_term) < break_check) { break; } b0 = sqrt(a0 * b0); a0 = an; if(!phi_is_pi_half) { phi_n += numbers::pi_v * static_cast((phi_n / numbers::pi_v) + half); } } Fpm = phi_n / an; if(!phi_is_pi_half) { Fpm /= p2; } Km = my_pi_half / an; if(has_e) { const T one_minus_cn_2ncn_inner_prod_half = one - cn_2ncn_inner_prod; if(pEm != nullptr) { *pEm = Km * one_minus_cn_2ncn_inner_prod_half; } if(pEpm != nullptr) { *pEpm = (Fpm * one_minus_cn_2ncn_inner_prod_half) + sin_phi_n_cn_inner_prod; } } } } } // namespace elliptic_series } //namespace ellint_detail } //namespace detail } //namespace decimal } //namespace boost #endif //BOOST_DECIMAL_DETAIL_CMATH_IMPL_ELLINT_IMPL_HPP