diff --git a/src/LambertW.cc b/src/LambertW.cc new file mode 100644 index 0000000000000000000000000000000000000000..0c0ae500c26a2174cb507e9779fa26e1ce00dd3f --- /dev/null +++ b/src/LambertW.cc @@ -0,0 +1,387 @@ +/* + Implementation of Lambert W function + + Copyright (C) 2009 Darko Veberic, darko.veberic@ung.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 <http://www.gnu.org/licenses/>. +*/ + +#include <iostream> +#include <cmath> +#include <limits> + +using namespace std; + +namespace LambertWDetail { + +const double kInvE = 1 / M_E; + +template <int n> inline double BranchPointPolynomial(const double p); + +template <> inline double BranchPointPolynomial<1>(const double p) { + return -1 + p; +} + +template <> inline double BranchPointPolynomial<2>(const double p) { + return -1 + p * (1 + p * (-1. / 3)); +} + +template <> inline double BranchPointPolynomial<3>(const double p) { + return -1 + p * (1 + p * (-1. / 3 + p * (11. / 72))); +} + +template <> inline double BranchPointPolynomial<4>(const double p) { + return -1 + p * (1 + p * (-1. / 3 + p * (11. / 72 + p * (-43. / 540)))); +} + +template <> inline double BranchPointPolynomial<5>(const double p) { + return -1 + + p * (1 + p * (-1. / 3 + + p * (11. / 72 + p * (-43. / 540 + p * (769. / 17280))))); +} + +template <> inline double BranchPointPolynomial<6>(const double p) { + return -1 + p * (1 + p * (-1. / 3 + + p * (11. / 72 + + p * (-43. / 540 + p * (769. / 17280 + + p * (-221. / 8505)))))); +} + +template <> inline double BranchPointPolynomial<7>(const double p) { + return -1 + p * (1 + p * (-1. / 3 + + p * (11. / 72 + + p * (-43. / 540 + + p * (769. / 17280 + + p * (-221. / 8505 + + p * (680863. / 43545600))))))); +} + +template <> inline double BranchPointPolynomial<8>(const double p) { + return -1 + + p * (1 + p * (-1. / 3 + + p * (11. / 72 + + p * (-43. / 540 + + p * (769. / 17280 + + p * (-221. / 8505 + + p * (680863. / 43545600 + + p * (-1963. / 204120)))))))); +} + +template <> inline double BranchPointPolynomial<9>(const double p) { + return -1 + + p * (1 + p * (-1. / 3 + + p * (11. / 72 + + p * (-43. / 540 + + p * (769. / 17280 + + p * (-221. / 8505 + + p * (680863. / 43545600 + + p * (-1963. / 204120 + + p * (226287557. / + 37623398400.))))))))); +} + +template <int order> +inline double AsymptoticExpansion(const double a, const double b); + +template <> +inline double AsymptoticExpansion<0>(const double a, const double b) { + return a - b; +} + +template <> +inline double AsymptoticExpansion<1>(const double a, const double b) { + return a - b + b / a; +} + +template <> +inline double AsymptoticExpansion<2>(const double a, const double b) { + const double ia = 1 / a; + return a - b + b / a * (1 + ia * 0.5 * (-2 + b)); +} + +template <> +inline double AsymptoticExpansion<3>(const double a, const double b) { + const double ia = 1 / a; + return a - b + b / a * (1 + ia * (0.5 * (-2 + b) + + ia * 1 / 6. * (6 + b * (-9 + b * 2)))); +} + +template <> +inline double AsymptoticExpansion<4>(const double a, const double b) { + const double ia = 1 / a; + return a - b + + b / a * (1 + ia * (0.5 * (-2 + b) + + ia * (1 / 6. * (6 + b * (-9 + b * 2)) + + ia * 1 / 12. * + (-12 + b * (36 + b * (-22 + b * 3)))))); +} + +template <> +inline double AsymptoticExpansion<5>(const double a, const double b) { + const double ia = 1 / a; + return a - b + + b / a * + (1 + + ia * (0.5 * (-2 + b) + + ia * (1 / 6. * (6 + b * (-9 + b * 2)) + + ia * (1 / 12. * (-12 + b * (36 + b * (-22 + b * 3))) + + ia * 1 / 60. * + (60 + + b * (-300 + + b * (350 + b * (-125 + b * 12)))))))); +} + +template <int branch> class Branch { + +public: + template <int order> static double BranchPointExpansion(const double x) { + return BranchPointPolynomial<order>(eSign * sqrt(2 * (M_E * x + 1))); + } + + // Asymptotic expansion + // Corless et al. 1996, de Bruijn (1981) + template <int order> static double AsymptoticExpansion(const double x) { + const double logsx = log(eSign * x); + const double logslogsx = log(eSign * logsx); + return LambertWDetail::AsymptoticExpansion<order>(logsx, logslogsx); + } + + template <int n> static inline double RationalApproximation(const double x); + + // Logarithmic recursion + template <int order> static inline double LogRecursion(const double x) { + return LogRecursionStep<order>(log(eSign * x)); + } + + // generic approximation valid to at least 5 decimal places + static inline double Approximation(const double x); + +private: + // enum { eSign = 2*branch + 1 }; this doesn't work on gcc 3.3.3 + static const int eSign = 2 * branch + 1; + + template <int n> static inline double LogRecursionStep(const double logsx) { + return logsx - log(eSign * LogRecursionStep<n - 1>(logsx)); + } +}; + +// Rational approximations + +template <> +template <> +inline double Branch<0>::RationalApproximation<0>(const double x) { + return x * (60 + x * (114 + 17 * x)) / (60 + x * (174 + 101 * x)); +} + +template <> +template <> +inline double Branch<0>::RationalApproximation<1>(const double x) { + // branch 0, valid for [-0.31,0.3] + return x * + (1 + x * (5.931375839364438 + + x * (11.392205505329132 + + x * (7.338883399111118 + x * 0.6534490169919599)))) / + (1 + x * (6.931373689597704 + + x * (16.82349461388016 + + x * (16.43072324143226 + x * 5.115235195211697)))); +} + +template <> +template <> +inline double Branch<0>::RationalApproximation<2>(const double x) { + // branch 0, valid for [-0.31,0.5] + return x * (1 + x * (4.790423028527326 + + x * (6.695945075293267 + x * 2.4243096805908033))) / + (1 + x * (5.790432723810737 + + x * (10.986445930034288 + + x * (7.391303898769326 + x * 1.1414723648617864)))); +} + +template <> +template <> +inline double Branch<0>::RationalApproximation<3>(const double x) { + // branch 0, valid for [0.3,7] + return x * + (1 + + x * (2.4450530707265568 + + x * (1.3436642259582265 + + x * (0.14844005539759195 + x * 0.0008047501729129999)))) / + (1 + x * (3.4447089864860025 + + x * (3.2924898573719523 + + x * (0.9164600188031222 + x * 0.05306864044833221)))); +} + +template <> +template <> +inline double Branch<-1>::RationalApproximation<4>(const double x) { + // branch -1, valid for [-0.3,-0.05] + return (-7.814176723907436 + + x * (253.88810188892484 + x * 657.9493176902304)) / + (1 + + x * (-60.43958713690808 + + x * (99.98567083107612 + + x * (682.6073999909428 + + x * (962.1784396969866 + x * 1477.9341280760887))))); +} + +// stopping conditions + +template <> +template <> +inline double Branch<0>::LogRecursionStep<0>(const double logsx) { + return logsx; +} + +template <> +template <> +inline double Branch<-1>::LogRecursionStep<0>(const double logsx) { + return logsx; +} + +template <> inline double Branch<0>::Approximation(const double x) { + if (x < -0.32358170806015724) { + if (x < -kInvE) + return numeric_limits<double>::quiet_NaN(); + else if (x < -kInvE + 1e-5) + return BranchPointExpansion<5>(x); + else + return BranchPointExpansion<9>(x); + } else { + if (x < 0.14546954290661823) + return RationalApproximation<1>(x); + else if (x < 8.706658967856612) + return RationalApproximation<3>(x); + else + return AsymptoticExpansion<5>(x); + } +} + +template <> inline double Branch<-1>::Approximation(const double x) { + if (x < -0.051012917658221676) { + if (x < -kInvE + 1e-5) { + if (x < -kInvE) + return numeric_limits<double>::quiet_NaN(); + else + return BranchPointExpansion<5>(x); + } else { + if (x < -0.30298541769) + return BranchPointExpansion<9>(x); + else + return RationalApproximation<4>(x); + } + } else { + if (x < 0) + return LogRecursion<9>(x); + else if (x == 0) + return -numeric_limits<double>::infinity(); + else + return numeric_limits<double>::quiet_NaN(); + } +} + +// iterations + +inline double HalleyStep(const double x, const double w) { + const double ew = exp(w); + const double wew = w * ew; + const double wewx = wew - x; + const double w1 = w + 1; + return w - wewx / (ew * w1 - (w + 2) * wewx / (2 * w1)); +} + +inline double FritschStep(const double x, const double w) { + const double z = log(x / w) - w; + const double w1 = w + 1; + const double q = 2 * w1 * (w1 + (2 / 3.) * z); + const double eps = z / w1 * (q - z) / (q - 2 * z); + return w * (1 + eps); +} + +template <double IterationStep(const double x, const double w)> +inline double Iterate(const double x, double w, const double eps = 1e-6) { + for (int i = 0; i < 100; ++i) { + const double ww = IterationStep(x, w); + if (fabs(ww - w) <= eps) + return ww; + w = ww; + } + cerr << "convergence not reached." << endl; + return w; +} + +template <double IterationStep(const double x, const double w)> +struct Iterator { + + static double Do(const int n, const double x, const double w) { + for (int i = 0; i < n; ++i) + w = IterationStep(x, w); + return w; + } + + template <int n> static double Do(const double x, const double w) { + for (int i = 0; i < n; ++i) + w = IterationStep(x, w); + return w; + } + + template <int n, class = void> struct Depth { + static double Recurse(const double x, double w) { + return Depth<n - 1>::Recurse(x, IterationStep(x, w)); + } + }; + + // stop condition + template <class T> struct Depth<1, T> { + static double Recurse(const double x, const double w) { + return IterationStep(x, w); + } + }; + + // identity + template <class T> struct Depth<0, T> { + static double Recurse(const double x, const double w) { return w; } + }; +}; + +} // LambertWDetail + +template <int branch> double LambertWApproximation(const double x) { + return LambertWDetail::Branch<branch>::Approximation(x); +} + +// instantiations +template double LambertWApproximation<0>(const double x); +template double LambertWApproximation<-1>(const double x); + +template <int branch> double LambertW(const double x); + +template <> double LambertW<0>(const double x) { + if (fabs(x) > 1e-6 && x > -LambertWDetail::kInvE + 1e-5) + return LambertWDetail::Iterator<LambertWDetail::FritschStep>::Depth< + 1>::Recurse(x, LambertWApproximation<0>(x)); + else + return LambertWApproximation<0>(x); +} + +template <> double LambertW<-1>(const double x) { + if (x > -LambertWDetail::kInvE + 1e-5) + return LambertWDetail::Iterator<LambertWDetail::FritschStep>::Depth< + 1>::Recurse(x, LambertWApproximation<-1>(x)); + else + return LambertWApproximation<-1>(x); +} + +// instantiations +template double LambertW<0>(const double x); +template double LambertW<-1>(const double x); diff --git a/src/LambertW.h b/src/LambertW.h new file mode 100644 index 0000000000000000000000000000000000000000..9bfd69f6127e696f3d78c34bf9f1ceeca5993dfc --- /dev/null +++ b/src/LambertW.h @@ -0,0 +1,82 @@ +/* + Implementation of Lambert W function + + Copyright (C) 2009 Darko Veberic, darko.veberic@ung.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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _LambertW_h_ +#define _LambertW_h_ + +#include <limits> + + +/** + \author Darko Veberic + \version $Id: LambertW.h 14958 2009-10-19 11:11:19Z darko $ + \date 25 Jun 2009 +*/ + + +/** Approximate Lambert W function + Accuracy at least 5 decimal places in all definition range. + See LambertW() for details. + + \param branch: valid values are 0 and -1 + \param x: real-valued argument \f$\geq-1/e\f$ + \ingroup math +*/ + +template<int branch> +double LambertWApproximation(const double x); + + +/** Lambert W function + \image html LambertW.png + + Lambert function \f$y={\rm W}(x)\f$ is defined as a solution + to the \f$x=ye^y\f$ expression and is also known as + "product logarithm". Since the inverse of \f$ye^y\f$ is not + single-valued, the Lambert function has two real branches + \f${\rm W}_0\f$ and \f${\rm W}_{-1}\f$. + + \f${\rm W}_0(x)\f$ has real values in the interval + \f$[-1/e,\infty]\f$ and \f${\rm W}_{-1}(x)\f$ has real values + in the interval \f$[-1/e,0]\f$. + Accuracy is the nominal double type resolution + (16 decimal places). + + \param branch: valid values are 0 and -1 + \param x: real-valued argument \f$\geq-1/e\f$ (range depends on branch) + \ingroup math +*/ + +template<int branch> +double LambertW(const double x); + + +inline +double +LambertW(const int branch, const double x) +{ + switch (branch) { + case -1: return LambertW<-1>(x); + case 0: return LambertW<0>(x); + default: return std::numeric_limits<double>::quiet_NaN(); + } +} + + +#endif