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