Download or view LambertW.frink in plain text format
use ArbitraryPrecision.frink
// This calculates the value of the Lambert W function, also
// sometimes called the Product Log function. This calculates the principal
// branch sometimes known as W_0. (See LambertW1 for calculating the other
// branch)
//
// The Lambert W function is what you get when you try to solve
// z = w e^w for w.
//
// You really want to be using this with Frink:The Next Generation
//
// See: http://mathworld.wolfram.com/LambertW-Function.html
// and http://en.wikipedia.org/wiki/Lambert_W_function
// for a general solution.
//
// This algorithm was originally based on the algorithm for calculating
// LambertW using Newton's method, as outlined at:
// http://www.whim.org/nebula/math/lambertw.html
// but it actually now uses Halley's method because it converges
// faster.
//
// This also uses as its initial guess very good approximation to Lambert W
// with a relative error of less than 0.01 in the entire complex plane.
// From the paper:
// "Uniform approximations for transcendental functions", Serge Winitzki
//
// For this, see "Numerical Evaluation of the Lambert W Function
// and Application to Generation of Generalized Gaussian Noise
// With Exponent 1/2" by Francois Chapeau-Blondeau and Abdelilah Monir,
// IEEE Transactions on Signal Processing, Vol. 50, No. 9,
// September 2002
// https://pdfs.semanticscholar.org/7a5a/76a9369586dd0dd34dda156d8f2779d1fd59.pdf
//
LambertW[x, digits=getPrecision[]] :=
{
// println["In LambertW, x = $x, digits = $digits"]
oldPrec = getPrecision[]
err = 10^-(digits+2)
// println["Error is $err"]
if x == 0
return 0
// TODO: Get cached value from E class using binary splitting.
e = arbitraryExp[1, digits+5]
// println["e is $e"]
// Get a good first guess.
w = estimateLambertW[x]
// println["Estimate is $w"]
setPrecision[digits+4]
retval = 1
iterCount = 0
try
{
do
{
oldw = w
iterCount = iterCount + 1
if (w == -1)
return -1
else
if (w < -.35) and (iterCount mod 3 == 2)
{
// Newton's method sometimes bounces around
// -1/e (approx -0.367879) so we sometimes
// use this alternate method to try and make it converge.
w = -1 + (w+1) sqrt[(x + 1/e) / (w arbitraryExp[w] + 1/e),
digits+4]
if w < -1
w = -1
} else
{
// Normal case, Newton's method
// We normally use Halley's method (below) which is more
// complicated but has a higher order of convergence, converging
// in about half the number of steps in the usual case. We use
// Newton's method if we haven't converged after a long time
// just in case something's going weird in Halley's method.
if (iterCount > 20) and (iterCount mod 2 == 0)
w = (x arbitraryExp[-w] + w^2) / (w+1)
else
{
// Halley's method, see Eq. 9 in Chapeau-Blondeau
ew = arbitraryExp[w]
wew = w ew
wewmz = wew - x
w = w - wewmz / ((w + 1) ew - ((w+2) wewmz)/(2 w + 2))
}
}
// println[w]
} while (abs[w - oldw] > abs[w err])
setPrecision[digits]
retval = 1.0 * w
}
finally
{
setPrecision[oldPrec]
}
// println["In LambertW, returning $retval"]
return retval
}
/** Function to test the difference between the calculated value of LambertW
and its inverse function. */
LambertWTest[x, digits=getPrecision[]] :=
{
origPrec = getPrecision[]
try
{
setPrecision[digits]
w = LambertW[x, digits]
reverse = w arbitraryExp[w, digits]
// println["$x\t$w"]
return x - reverse
}
finally
setPrecision[origPrec]
}
// This procedure gives a very good approximation to Lambert W
// with a relative error of less than 0.01 in the entire complex plane.
// From the paper:
// "Uniform approximations for transcendental functions", Serge Winitzki
estimateLambertW[x] :=
{
if (x < 1)
{
ex = e x
return ex / (1 + ((2ex + 2)^(-1/2) + 1.2890834)^-1) //eq39
} else
{
ln1x = ln[1+x]
return ln1x (1 - (ln[1 + ln1x])/(2 + ln1x)) // Eq. 38
}
}
/** This calculates the secondary branch of the Lambert W function known as
W_-1. It is defined over the domain [-1/e, 0]. */
LambertW1[x, digits=getPrecision[]] :=
{
oldPrec = getPrecision[]
err = 10^-(digits+2)
// println["Error is $err"]
if x == 0
return 0
// Get a good first guess.
w = estimateLambertW1[x]
// println[w]
if w == undef or w >= -1
w = -1. - 10^-(min[oldPrec,digits]/2)
// println[w]
setPrecision[digits+4]
retval = 1
iterCount = 0
try
{
do
{
oldw = w
iterCount = iterCount + 1
// Normal case, Newton's method
// We normally use Halley's method (below) which is more
// complicated but has a higher order of convergence, converging
// in about half the number of steps in the usual case. We use
// Newton's method if we haven't converged after a long time
// just in case something's going weird in Halley's method.
if (iterCount > 10) and (iterCount mod 3 == 0)
w = (x arbitraryExp[-w] + w^2) / (w+1)
else
{
// Halley's method, see Eq. 9 in Chapeau-Blondeau
ew = arbitraryExp[w]
wew = w ew
wewmz = wew - x
w = w - wewmz / ((w + 1) ew - ((w+2) wewmz)/(2 w + 2))
// println[w]
}
} while (abs[w - oldw] > abs[w err])
setPrecision[digits]
retval = 1.0 * w
}
finally
{
setPrecision[oldPrec]
}
return retval
}
/** This function gives a good initial guess for the Lambert W-1 function.
The relative error is less than 10^-4 for any value in its domain [-1/e, 0]
This should be used to seed a more accurate convergence procedure.
This algorithm is Fig. 2 in "Numerical Evaluation of the Lambert W Function
and Application to Generation of Generalized Gaussian Noise
With Exponent 1/2" by Francois Chapeau-Blondeau and Abdelilah Monir,
IEEE Transactions on Signal Processing, Vol. 50, No. 9,
September 2002
https://pdfs.semanticscholar.org/7a5a/76a9369586dd0dd34dda156d8f2779d1fd59.pdf
*/
estimateLambertW1[z] :=
{
if -1/e <= z and z < -0.333
{
p = -sqrt[2 (e z + 1)]
return -1 + p - 1/3 p^2 + 11/72 p^3 - 43/540 p^4 + 769/17280 p^5 - 221/8505 p^6
} else
if -0.333 <= z and z <= -0.033
{
return (-8.0960 + 391.0025 z - 47.4252 z^2 - 4877.6330 z^3 - 5532.7760 z^4) / (1 - 82.9423 z + 433.8688 z^2 + 1515.3060 z^3)
} else
if -0.033 < z and z < 0
{
L1 = ln[-z]
L2 = ln[-ln[-z]]
return L1 - L2 + L2/L1 + ((-2+L2) L2)/(2 L1^2) +
((6 - 9 L2 + 2 L2^2)L2)/(6 L1^3) +
((-12 + 36 L2 - 22 L2^2 + 3 L2^3) L2)/ (12 L1^4) +
((60 - 300 L2 + 350 L2^2 - 125 L2^3 + 12 L2^4) L2)/ (60 L1^5)
}
return undef
}
/*
digits = 500
for z = -1/e + 1e-15 to 20 step 0.1
{
err = LambertWTest[z, digits]
if abs[err] > 10^-(digits-1)
println["$z\t$err"]
}
*/
Download or view LambertW.frink in plain text format
This is a program written in the programming language Frink.
For more information, view the Frink
Documentation or see More Sample Frink Programs.
Alan Eliasen was born 20143 days, 9 hours, 51 minutes ago.