/* The functions exp1, expi below are based on translations of the Fortran code * by Shanjie Zhang and Jianming Jin from the book * * Shanjie Zhang, Jianming Jin, * Computation of Special Functions, * Wiley, 1996, * ISBN: 0-471-11963-6, * LC: QA351.C45. */ #pragma once #include "config.h" #include "error.h" #include "cephes/const.h" namespace xsf { XSF_HOST_DEVICE inline double exp1(double x) { // ============================================ // Purpose: Compute exponential integral E1(x) // Input : x --- Argument of E1(x) // Output: E1 --- E1(x) ( x > 0 ) // ============================================ int k, m; double e1, r, t, t0; constexpr double ga = cephes::detail::SCIPY_EULER; if (x == 0.0) { return std::numeric_limits::infinity(); } if (x <= 1.0) { e1 = 1.0; r = 1.0; for (k = 1; k < 26; k++) { r = -r*k*x/std::pow(k+1.0, 2); e1 += r; if (std::abs(r) <= std::abs(e1)*1e-15) { break; } } return -ga - std::log(x) + x*e1; } m = 20 + (int)(80.0/x); t0 = 0.0; for (k = m; k > 0; k--) { t0 = k / (1.0 + k / (x+t0)); } t = 1.0 / (x + t0); return std::exp(-x)*t; } XSF_HOST_DEVICE inline float exp1(float x) { return exp1(static_cast(x)); } XSF_HOST_DEVICE inline std::complex exp1(std::complex z) { // ==================================================== // Purpose: Compute complex exponential integral E1(z) // Input : z --- Argument of E1(z) // Output: CE1 --- E1(z) // ==================================================== constexpr double el = cephes::detail::SCIPY_EULER; int k; std::complex ce1, cr, zc, zd, zdc; double x = z.real(); double a0 = std::abs(z); // Continued fraction converges slowly near negative real axis, // so use power series in a wedge around it until radius 40.0 double xt = -2.0*std::abs(z.imag()); if (a0 == 0.0) { return std::numeric_limits::infinity(); } if ((a0 < 5.0) || ((x < xt) && (a0 < 40.0))) { // Power series ce1 = 1.0; cr = 1.0; for (k = 1; k < 501; k++) { cr = -cr*z*static_cast(k / std::pow(k + 1, 2)); ce1 += cr; if (std::abs(cr) < std::abs(ce1)*1e-15) { break; } } if ((x <= 0.0) && (z.imag() == 0.0)) { //Careful on the branch cut -- use the sign of the imaginary part // to get the right sign on the factor if pi. ce1 = -el - std::log(-z) + z*ce1 - std::copysign(M_PI, z.imag())*std::complex(0.0, 1.0); } else { ce1 = -el - std::log(z) + z*ce1; } } else { // Continued fraction https://dlmf.nist.gov/6.9 // 1 1 1 2 2 3 3 // E1 = exp(-z) * ----- ----- ----- ----- ----- ----- ----- ... // Z + 1 + Z + 1 + Z + 1 + Z + zc = 0.0; zd = static_cast(1) / z; zdc = zd; zc += zdc; for (k = 1; k < 501; k++) { zd = static_cast(1) / (zd*static_cast(k) + static_cast(1)); zdc *= (zd - static_cast(1)); zc += zdc; zd = static_cast(1) / (zd*static_cast(k) + z); zdc *= (z*zd - static_cast(1)); zc += zdc; if ((std::abs(zdc) <= std::abs(zc)*1e-15) && (k > 20)) { break; } } ce1 = std::exp(-z)*zc; if ((x <= 0.0) && (z.imag() == 0.0)) { ce1 -= M_PI*std::complex(0.0, 1.0); } } return ce1; } XSF_HOST_DEVICE inline std::complex exp1(std::complex z) { return static_cast>(exp1(static_cast>(z))); } XSF_HOST_DEVICE inline double expi(double x) { // ============================================ // Purpose: Compute exponential integral Ei(x) // Input : x --- Argument of Ei(x) // Output: EI --- Ei(x) // ============================================ constexpr double ga = cephes::detail::SCIPY_EULER; double ei, r; if (x == 0.0) { ei = -std::numeric_limits::infinity(); } else if (x < 0) { ei = -exp1(-x); } else if (std::abs(x) <= 40.0) { // Power series around x=0 ei = 1.0; r = 1.0; for (int k = 1; k <= 100; k++) { r = r * k * x / ((k + 1.0) * (k + 1.0)); ei += r; if (std::abs(r / ei) <= 1.0e-15) { break; } } ei = ga + std::log(x) + x * ei; } else { // Asymptotic expansion (the series is not convergent) ei = 1.0; r = 1.0; for (int k = 1; k <= 20; k++) { r = r * k / x; ei += r; } ei = std::exp(x) / x * ei; } return ei; } XSF_HOST_DEVICE inline float expi(float x) { return expi(static_cast(x)); } std::complex expi(std::complex z) { // ============================================ // Purpose: Compute exponential integral Ei(x) // Input : x --- Complex argument of Ei(x) // Output: EI --- Ei(x) // ============================================ std::complex cei; cei = - exp1(-z); if (z.imag() > 0.0) { cei += std::complex(0.0, M_PI); } else if (z.imag() < 0.0 ) { cei -= std::complex(0.0, M_PI); } else { if (z.real() > 0.0) { cei += std::complex(0.0, copysign(M_PI, z.imag())); } } return cei; } XSF_HOST_DEVICE inline std::complex expi(std::complex z) { return static_cast>(expi(static_cast>(z))); } namespace detail { // // Compute a factor of the exponential integral E1. // This is used in scaled_exp1(x) for moderate values of x. // // The function uses the continued fraction expansion given in equation 5.1.22 // of Abramowitz & Stegun, "Handbook of Mathematical Functions". // For n=1, this is // // E1(x) = exp(-x)*C(x) // // where C(x), expressed in the notation used in A&S, is the continued fraction // // 1 1 1 2 2 3 3 // C(x) = --- --- --- --- --- --- --- ... // x + 1 + x + 1 + x + 1 + x + // // Here, we pull a factor of 1/z out of C(x), so // // E1(x) = (exp(-x)/x)*F(x) // // and a bit of algebra gives the continued fraction expansion of F(x) to be // // 1 1 1 2 2 3 3 // F(x) = --- --- --- --- --- --- --- ... // 1 + x + 1 + x + 1 + x + 1 + // XSF_HOST_DEVICE inline double expint1_factor_cont_frac(double x) { // The number of terms to use in the truncated continued fraction // depends on x. Larger values of x require fewer terms. int m = 20 + (int) (80.0 / x); double t0 = 0.0; for (int k = m; k > 0; --k) { t0 = k / (x + k / (1 + t0)); } return 1 / (1 + t0); } } // namespace detail // // Scaled version of the exponential integral E_1(x). // // Factor E_1(x) as // // E_1(x) = exp(-x)/x * F(x) // // This function computes F(x). // // F(x) has the properties: // * F(0) = 0 // * F is increasing on [0, inf) // * lim_{x->inf} F(x) = 1. // XSF_HOST_DEVICE inline double scaled_exp1(double x) { if (x < 0) { return std::numeric_limits::quiet_NaN(); } if (x == 0) { return 0.0; } if (x <= 1) { // For small x, the naive implementation is sufficiently accurate. return x * std::exp(x) * exp1(x); } if (x <= 1250) { // For moderate x, use the continued fraction expansion. return detail::expint1_factor_cont_frac(x); } // For large x, use the asymptotic expansion. This is equation 5.1.51 // from Abramowitz & Stegun, "Handbook of Mathematical Functions". return 1 + (-1 + (2 + (-6 + (24 - 120 / x) / x) / x) / x) / x; } XSF_HOST_DEVICE inline float scaled_exp1(float x) { return scaled_exp1(static_cast(x)); } } // namespace xsf