由于一些很蠢的原因,我写了一份完全用不着的用于计算 Lambert W function 的 C# 代码. 具体原理是很粗暴的牛顿法求解,但有几个特别处理的地方:
首先是估算. W0比较好处理,其实随便给个初始值就好,我这里选择了偏移后的 ex. 而 W-1 则比较特殊,首先在 x < -2 的部分变成了 concave 的,而且越往左斜率越小,所以一律从 x = -2 开始尝试;而在 -2 < x < -1 的区间内其实也是随便给个初始值就好,我这里选择了偏移后的 cos 函数.
其次是精度处理. 通过 log2x 做差可以求出二进制下的有效数位,然而当 y 值较大的时候其实并不能把完整的 52 位双精度浮点数的小数部分求完整,大概只能求到 47 位左右,所以这里我保守选择了 42 位有效数字.
代码:
| 
					 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  | 
						using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SysMath = System.Math; namespace MartinCl2.Math {     /// <summary>     /// Lambert W function is the inverse function of x*e^x.     /// It has two branches in [-1/e, +inf).     /// </summary>     class LambertW     {         /// <summary>         /// Use Newton's method to find the numerical solution.         /// </summary>         /// <param name="branch"></param>         /// <param name="input"></param>         /// <returns></returns>         public static double Evaluate(int branch, double input)         {             // Check argument range             if (input < -1 / SysMath.E)             {                 throw new ArgumentOutOfRangeException                     ("input", "input should be larger than -1/e.");             }             if (branch != 0 && branch != -1)             {                 throw new ArgumentOutOfRangeException                     ("branch", "branch should be 0 or -1.");             }             if (input > 0 && branch == -1)             {                 throw new ArgumentOutOfRangeException                     ("Only branch 0 has positive input.");             }             // Start with an estimation             double result = Estimate(branch, input);             // Iterate using Newton's method             int loopsRemaining = 100;             while (TestResult(result, input) && loopsRemaining > 0)             {                 double x0 = result;                 double y0 = result * SysMath.Exp(result);                 double k = SysMath.Exp(x0) * (1 + x0);                 double y1 = input;                 double x1 = (y1 - y0) / k + x0;                 result = x1;                 loopsRemaining--;             }             if (loopsRemaining == 0)             {                 throw new Exception("Cannot find solution.");             }             return result;         }         /// <summary>         /// Give a rough estimation of Lambert W function         /// </summary>         /// <param name="branch"></param>         /// <param name="input"></param>         /// <returns></returns>         private static double Estimate(int branch, double input)         {             if (branch == -1)             {                 // return SysMath.Max(-2, -0.909091 + (8.97204 *                 //     SysMath.Sqrt(0.00138945 - 0.0102667 * input                 //     * input)) / input);                 if (input > -0.2706706)                 {                     // Cannot go further less otherwise Newton's method                     // won't work                     return -2;                 }                 else                 {                     return -1 - SysMath.Acos(-4.11727 * input - 0.514659);                 }             }             else if (branch == 0)             {                 return -1 + SysMath.Log(1 / SysMath.E + SysMath.E + input);             }             else             {                 throw new ArgumentException();             }         }         /// <summary>         /// Test whether f(x)=x*e^x and y is close enough.         /// </summary>         /// <param name="x"></param>         /// <param name="y"></param>         /// <returns></returns>         private static bool TestResult(double x, double y)         {             double fx = x * SysMath.Exp(x);             double delta = fx - y;             double deltaBits = SysMath.Log(SysMath.Abs(y / delta), 2);             // Double-precision floating-point has 52 bits fraction.             // We allow 10 bit tolarance             return deltaBits < 42;         }     } }  | 
					


0 条评论。