File size: 8,345 Bytes
7885a28 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
/* 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<double>::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<double>(x)); }
XSF_HOST_DEVICE inline std::complex<double> exp1(std::complex<double> 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<double> 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<double>::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<double>(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<double>(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<double>(1) / z;
zdc = zd;
zc += zdc;
for (k = 1; k < 501; k++) {
zd = static_cast<double>(1) / (zd*static_cast<double>(k) + static_cast<double>(1));
zdc *= (zd - static_cast<double>(1));
zc += zdc;
zd = static_cast<double>(1) / (zd*static_cast<double>(k) + z);
zdc *= (z*zd - static_cast<double>(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<double>(0.0, 1.0);
}
}
return ce1;
}
XSF_HOST_DEVICE inline std::complex<float> exp1(std::complex<float> z) {
return static_cast<std::complex<float>>(exp1(static_cast<std::complex<double>>(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<double>::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<double>(x)); }
std::complex<double> expi(std::complex<double> z) {
// ============================================
// Purpose: Compute exponential integral Ei(x)
// Input : x --- Complex argument of Ei(x)
// Output: EI --- Ei(x)
// ============================================
std::complex<double> cei;
cei = - exp1(-z);
if (z.imag() > 0.0) {
cei += std::complex<double>(0.0, M_PI);
} else if (z.imag() < 0.0 ) {
cei -= std::complex<double>(0.0, M_PI);
} else {
if (z.real() > 0.0) {
cei += std::complex<double>(0.0, copysign(M_PI, z.imag()));
}
}
return cei;
}
XSF_HOST_DEVICE inline std::complex<float> expi(std::complex<float> z) {
return static_cast<std::complex<float>>(expi(static_cast<std::complex<double>>(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<double>::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<double>(x)); }
} // namespace xsf
|