/* Implementation of the Fukushima method for the Lambert W function Copyright (C) 2015 Darko Veberic, darko.veberic@ijs.si This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* This code is based on the following publication and its author's fortran code: Toshio Fukushima, "Precise and fast computation of Lambert W-functions without transcendental function evaluations", J. Comp. Appl. Math. 244 (2013) 77-89. */ #include #include #include #include using namespace std; namespace Fukushima { double LambertWSeries(const double p) { static const double q[] = { -1, +1, -0.333333333333333333, +0.152777777777777778, -0.0796296296296296296, +0.0445023148148148148, -0.0259847148736037625, +0.0156356325323339212, -0.00961689202429943171, +0.00601454325295611786, -0.00381129803489199923, +0.00244087799114398267, -0.00157693034468678425, +0.00102626332050760715, -0.000672061631156136204, +0.000442473061814620910, -0.000292677224729627445, +0.000194387276054539318, -0.000129574266852748819, +0.0000866503580520812717, -0.0000581136075044138168 }; const double ap = abs(p); if (ap < 0.01159) return -1 + p*(1 + p*(q[2] + p*(q[3] + p*(q[4] + p*(q[5] + p*q[6] ))))); else if (ap < 0.0766) return -1 + p*(1 + p*(q[2] + p*(q[3] + p*(q[4] + p*(q[5] + p*(q[6] + p*(q[7] + p*(q[8] + p*(q[9] + p*q[10] ))))))))); else return -1 + p*(1 + p*(q[2] + p*(q[3] + p*(q[4] + p*(q[5] + p*(q[6] + p*(q[7] + p*(q[8] + p*(q[9] + p*(q[10] + p*(q[11] + p*(q[12] + p*(q[13] + p*(q[14] + p*(q[15] + p*(q[16] + p*(q[17] + p*(q[18] + p*(q[19] + p*q[20] ))))))))))))))))))); } inline double LambertW0ZeroSeries(const double z) { return z*(1 - z*(1 - z*(1.5 - z*(2.6666666666666666667 - z*(5.2083333333333333333 - z*(10.8 - z*(23.343055555555555556 - z*(52.012698412698412698 - z*(118.62522321428571429 - z*(275.57319223985890653 - z*(649.78717234347442681 - z*(1551.1605194805194805 - z*(3741.4497029592385495 - z*(9104.5002411580189358 - z*(22324.308512706601434 - z*(55103.621972903835338 - z*136808.86090394293563 )))))))))))))))); } inline double FinalResult(const double w, const double y) { const double f0 = w - y; const double f1 = 1 + y; const double f00 = f0 * f0; const double f11 = f1 * f1; const double f0y = f0 * y; return w - 4 * f0 * (6 * f1 * (f11 + f0y) + f00 * y) / (f11 * (24 * f11 + 36 * f0y) + f00 * (6 * y * y + 8 * f1 * y + f0y)); } double LambertW0(const double z) { static double e[66]; static double g[65]; static double a[12]; static double b[12]; if (!e[0]) { const double e1 = 1 / M_E; double ej = 1; e[0] = M_E; e[1] = 1; g[0] = 0; for (int j = 1, jj = 2; jj < 66; ++jj) { ej *= M_E; e[jj] = e[j] * e1; g[j] = j * ej; j = jj; } a[0] = sqrt(e1); b[0] = 0.5; for (int j = 0, jj = 1; jj < 12; ++jj) { a[jj] = sqrt(a[j]); b[jj] = b[j] * 0.5; j = jj; } } if (abs(z) < 0.05) return LambertW0ZeroSeries(z); if (z < -0.35) { const double p2 = 2 * (M_E * z + 1); if (p2 > 0) return LambertWSeries(sqrt(p2)); if (p2 == 0) return -1; cerr << "(lambertw0) Argument out of range. z=" << z << endl; return numeric_limits::quiet_NaN(); } int n; for (n = 0; n <= 2; ++n) if (g[n] > z) goto line1; n = 2; for (int j = 1; j <= 5; ++j) { n *= 2; if (g[n] > z) goto line2; } cerr << "(lambertw0) Argument too large. z=" << z << endl; return numeric_limits::quiet_NaN(); line2: { int nh = n / 2; for (int j = 1; j <= 5; ++j) { nh /= 2; if (nh <= 0) break; if (g[n-nh] > z) n -= nh; } } line1: --n; int jmax = 8; if (z <= -0.36) jmax = 12; else if (z <= -0.3) jmax = 11; else if (n <= 0) jmax = 10; else if (n <= 1) jmax = 9; double y = z * e[n+1]; double w = n; for (int j = 0; j < jmax; ++j) { const double wj = w + b[j]; const double yj = y * a[j]; if (wj < yj) { w = wj; y = yj; } } return FinalResult(w, y); } double LambertWm1(const double z) { static double e[64]; static double g[64]; static double a[12]; static double b[12]; if (!e[0]) { const double e1 = 1 / M_E; double ej = e1; e[0] = M_E; g[0] = -e1; for (int j = 0, jj = 1; jj < 64; ++jj) { ej *= e1; e[jj] = e[j] * M_E; g[jj] = -(jj+1) * ej; j = jj; } a[0] = sqrt(M_E); b[0] = 0.5; for (int j = 0, jj = 1; jj < 12; ++jj) { a[jj] = sqrt(a[j]); b[jj] = b[j] * 0.5; j = jj; } } if (z >= 0) { cerr << "(lambertwm1) Argument out of range. z=" << z << endl; return numeric_limits::quiet_NaN(); } if (z < -0.35) { const double p2 = 2 * (M_E * z + 1); if (p2 > 0) return LambertWSeries(-sqrt(p2)); if (p2 == 0) return -1; cerr << "(lambertwm1) Argument out of range. z=" << z << endl; return numeric_limits::quiet_NaN(); } int n = 2; if (g[n - 1] > z) goto line1; for (int j = 1; j <= 5; ++j) { n *= 2; if (g[n - 1] > z) goto line2; } cerr << "(lambertwm1) Argument too small. z=" << z << endl; return numeric_limits::quiet_NaN(); line2: { int nh = n / 2; for (int j = 1; j <= 5; ++j) { nh /= 2; if (nh <= 0) break; if (g[n-nh - 1] > z) n -= nh; } } line1: --n; int jmax = 11; if (n >= 8) jmax = 8; else if (n >= 3) jmax = 9; else if (n >= 2) jmax = 10; double w = -n; double y = z * e[n - 1]; for (int j = 0; j < jmax; ++j) { const double wj = w - b[j]; const double yj = y * a[j]; if (wj < yj) { w = wj; y = yj; } } return FinalResult(w, y); } }