util/special_functions.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/util/special_functions.h" | ||
| 2 | |||
| 3 | #include <cmath> | ||
| 4 | #include <limits> | ||
| 5 | #include <numbers> | ||
| 6 | #include <stdexcept> | ||
| 7 | |||
| 8 | namespace terraingen { | ||
| 9 | |||
| 10 | namespace { | ||
| 11 | |||
| 12 | constexpr int kMaxIterations = 200; | ||
| 13 | constexpr double kEpsilon = std::numeric_limits<double>::epsilon(); | ||
| 14 | |||
| 15 | /// E_n(x) for x > 1 by its continued fraction, evaluated with Lentz's method. | ||
| 16 | 2012149 | double ExponentialIntegralFraction(int n, double x) { | |
| 17 | 2012149 | constexpr double kTiny = std::numeric_limits<double>::min() / kEpsilon; | |
| 18 | 2012149 | const int nm1 = n - 1; | |
| 19 | 2012149 | double b = x + n; | |
| 20 | 2012149 | double c = 1.0 / kTiny; | |
| 21 | 2012149 | double d = 1.0 / b; | |
| 22 | 2012149 | double h = d; | |
| 23 | 102196326 | for (int i = 1; i <= kMaxIterations; ++i) { | |
| 24 | 102196326 | const double a = -static_cast<double>(i) * (nm1 + i); | |
| 25 | 102196326 | b += 2.0; | |
| 26 | 102196326 | d = 1.0 / (a * d + b); | |
| 27 | 102196326 | c = b + a / c; | |
| 28 | 102196326 | const double delta = c * d; | |
| 29 | 102196326 | h *= delta; | |
| 30 | 102196326 | if (std::fabs(delta - 1.0) <= kEpsilon) { | |
| 31 | 2012149 | break; | |
| 32 | } | ||
| 33 | } | ||
| 34 | 2012149 | return h * std::exp(-x); | |
| 35 | } | ||
| 36 | |||
| 37 | /// E_n(x) for 0 < x <= 1 by its power series. | ||
| 38 | 1903802 | double ExponentialIntegralSeries(int n, double x) { | |
| 39 | 1903802 | const int nm1 = n - 1; | |
| 40 | 1903802 | double sum = nm1 != 0 ? 1.0 / nm1 : -std::log(x) - std::numbers::egamma; | |
| 41 | 1903802 | double factor = 1.0; | |
| 42 | 29410797 | for (int i = 1; i <= kMaxIterations; ++i) { | |
| 43 | 29410797 | factor *= -x / i; | |
| 44 | 29410797 | double delta = 0.0; | |
| 45 | 29410797 | if (i != nm1) { | |
| 46 | 27506998 | delta = -factor / (i - nm1); | |
| 47 | } else { | ||
| 48 | 1903799 | double psi = -std::numbers::egamma; | |
| 49 | 3807599 | for (int k = 1; k <= nm1; ++k) { | |
| 50 | 1903800 | psi += 1.0 / k; | |
| 51 | } | ||
| 52 | 1903799 | delta = factor * (-std::log(x) + psi); | |
| 53 | } | ||
| 54 | 29410797 | sum += delta; | |
| 55 | 29410797 | if (std::fabs(delta) <= std::fabs(sum) * kEpsilon) { | |
| 56 | 1903802 | break; | |
| 57 | } | ||
| 58 | } | ||
| 59 | 1903802 | return sum; | |
| 60 | } | ||
| 61 | |||
| 62 | } // namespace | ||
| 63 | |||
| 64 | 3915956 | double ExponentialIntegral(int n, double x) { | |
| 65 | 3915956 | if (n < 0 || !(std::isfinite(x)) || x < 0.0 || (x == 0.0 && n <= 1)) { | |
| 66 | 3 | throw std::domain_error("The exponential integral E_n(x) needs n >= 0 and x > 0"); | |
| 67 | } | ||
| 68 | 3915953 | if (n == 0) { | |
| 69 | 1 | return std::exp(-x) / x; | |
| 70 | } | ||
| 71 | 3915952 | if (x == 0.0) { | |
| 72 | 1 | return 1.0 / (n - 1); | |
| 73 | } | ||
| 74 | 3915951 | return x > 1.0 ? ExponentialIntegralFraction(n, x) : ExponentialIntegralSeries(n, x); | |
| 75 | } | ||
| 76 | |||
| 77 | } // namespace terraingen | ||
| 78 |