diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 795771c4a..023f26be6 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -684,7 +684,8 @@ and below `-1/e` there is none ([#2182](https://github.com/asc-community/Angouri al. (1996), as Mathematica's `ProductLog` and mpmath's `lambertw` have them: `W_0` is real on `[-1/e, +oo)` and `W_(-1)` on `[-1/e, 0)`. It is exact at `0` and where `z` is `c e^c` for a real `c` the branch takes, so `lambertw(e)` is `1` and `lambertw(-1/e)` is `-1`, and it is evaluated numerically elsewhere. Its derivative is `e^(-W(z))/(1 + W(z))` -([#2182](https://github.com/asc-community/AngouriMath/issues/2182)). +([#2182](https://github.com/asc-community/AngouriMath/issues/2182)). It compiles, in double precision on the same +branches, in both compilers ([#2195](https://github.com/asc-community/AngouriMath/issues/2195)). | Input | Was (2.6.0) | Now | |---|---|---| diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 7d45b2b7b..a24681bd6 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -363,6 +363,12 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [AngouriMath/Numerics/CombinatorialFunctions.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[AngouriMath/Numerics/LambertWFunction.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + +[Tests/UnitTests/Common/LambertWCompilationTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Common/CompileMatrixTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/LambertW.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/LambertW.cs index 3e01c4bff..ea0cb488b 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Number/LambertW.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/LambertW.cs @@ -29,10 +29,11 @@ partial record Number /// on [-1/e, 0), and a value on a branch cut is the one approached from above. /// /// - /// The branch is found in double precision, where it is cheap: from the series in - /// p = sqrt(2 (e z + 1)) about the branch point -1/e, from ln(1 + z) elsewhere on - /// the principal branch short of |z| = 3, and from ln z + 2 pi i k - ln(ln z + 2 pi i k) otherwise, - /// each then taken to a root by Halley's iteration. That root is refined by the same iteration + /// The branch is found in double precision, where it is cheap, by the kernel the compilers use, + /// : from the series in p = sqrt(2 (e z + 1)) + /// about the branch point -1/e, from ln(1 + z) elsewhere on the principal branch short of + /// |z| = 3, and from ln z + 2 pi i k - ln(ln z + 2 pi i k) otherwise, each then taken to a + /// root by Halley's iteration. That root is refined by the same iteration /// at the working precision, which triples the digits with each step and does not leave the /// branch. Near the branch point, where double precision cannot resolve z + 1/e, the /// series is evaluated at the working precision instead, and refined from there. @@ -78,7 +79,7 @@ partial record Number } else { - var start = InDoublePrecision(k, new NumericsComplex(re.ToDouble(), im.ToDouble())); + var start = AngouriMath.Numerics.LambertWFunction.LambertW(new NumericsComplex(k, 0), new NumericsComplex(re.ToDouble(), im.ToDouble())); if (double.IsNaN(start.Real) || double.IsNaN(start.Imaginary) || double.IsInfinity(start.Real) || double.IsInfinity(start.Imaginary)) return null; w = (EDecimal.FromDouble(start.Real), EDecimal.FromDouble(start.Imaginary)); @@ -107,40 +108,6 @@ partial record Number return Complex.Create(w.Re.RoundToPrecision(context), w.Im.RoundToPrecision(context)); } - /// - /// W_k(z) in double precision, from a start on the branch and Halley's iteration: the - /// branch is chosen here, and the working precision only refines the root. - /// - internal static NumericsComplex InDoublePrecision(int k, NumericsComplex z) - { - NumericsComplex w; - var p = NumericsComplex.Sqrt(2 * (Math.E * z + 1)); - // The series about the branch point, within 1/e of it, on the principal branch; ln(1 + z) - // short of the asymptotic reach, which W_0 is close to there and the asymptotic start is - // not: from that one, 0.5i went to another branch. Between -1 and -1/e on the axis - // ln(1 + z) is real where W_0 is not, and only the series reaches the root. - if (k == 0 && NumericsComplex.Abs(Math.E * z + 1) < 1) - w = -1 + p - p * p / 3 + 11 * p * p * p / 72; - else if (k == 0 && NumericsComplex.Abs(z) < 3) - w = NumericsComplex.Log(1 + z); - else - { - var l1 = NumericsComplex.Log(z) + new NumericsComplex(0, 2 * Math.PI * k); - var l2 = NumericsComplex.Log(l1); - w = l1 - l2 + l2 / l1; - } - for (var step = 0; step < 64; step++) - { - var ew = NumericsComplex.Exp(w); - var f = w * ew - z; - var change = f / (ew * (w + 1) - (w + 2) * f / (2 * w + 2)); - w -= change; - if (NumericsComplex.Abs(change) <= 1e-15 * Math.Max(NumericsComplex.Abs(w), 1)) - break; - } - return w; - } - private static (EDecimal Re, EDecimal Im) Exp((EDecimal Re, EDecimal Im) w, EContext work) { var modulus = w.Re.Exponential(work); diff --git a/Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs b/Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs index 8f9fb3c89..1f036fcc2 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoFE/Compiler.cs @@ -604,6 +604,19 @@ private protected override void CompileNode(Compiler compiler) } } + // The Lambert W function on its branch. https://github.com/asc-community/AngouriMath/issues/2195 + public partial record LambertWf + { + private protected override bool CompilesItself => true; + + private protected override void CompileNode(Compiler compiler) + { + Argument.InnerCompile(compiler); + Branch.InnerCompile(compiler); + compiler.Instructions.Add(new(InstructionType.CALL_LAMBERTW)); + } + } + public partial record Maxf { private protected override bool CompilesItself => true; diff --git a/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs b/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs index a0801319e..a2cf38559 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoFE/FastExpression.cs @@ -81,6 +81,7 @@ internal enum InstructionType CALL_BINOMIAL, CALL_FALLING_FACTORIAL, CALL_RISING_FACTORIAL, + CALL_LAMBERTW, // 3-arg: a value where its predicate holds, and the one below it elsewhere. CALL_SELECT = 150, @@ -355,6 +356,9 @@ public System.Numerics.Complex Substitute(params System.Numerics.Complex[] value case InstructionType.CALL_RISING_FACTORIAL: stack.Push(AngouriMath.Numerics.CombinatorialFunctions.RisingFactorial(stack.Pop(), stack.Pop())); break; + case InstructionType.CALL_LAMBERTW: + stack.Push(AngouriMath.Numerics.LambertWFunction.LambertW(stack.Pop(), stack.Pop())); + break; case InstructionType.CALL_DOUBLE_FACTORIAL: stack.Push(AngouriMath.Numerics.CombinatorialFunctions.DoubleFactorial(stack.Pop())); break; diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs index 328345960..61cea63ea 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/CompilationProtocol.cs @@ -242,6 +242,8 @@ FallingFactorialf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtL => Expression.Call(GetDef("FallingFactorial", 2, newLeft.Type), newLeft, newRight), RisingFactorialf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(right) is var newRight => Expression.Call(GetDef("RisingFactorial", 2, newLeft.Type), newLeft, newRight), + LambertWf when ShouldBeAtLeastDouble(left) is var newLeft && ShouldBeAtLeastDouble(right) is var newRight + => Expression.Call(GetDef("LambertW", 2, newLeft.Type), newLeft, newRight), Maxf => Expression.Call(GetDef("Max", 2, left.Type), left, right), Minf => Expression.Call(GetDef("Min", 2, left.Type), left, right), diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs index fc03d498b..ef23af0b7 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.cs @@ -342,6 +342,11 @@ public static System.Numerics.Complex Round(System.Numerics.Complex a) public static System.Numerics.Complex RisingFactorial(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.CombinatorialFunctions.RisingFactorial(a, b); public static double RisingFactorial(double a, double b) => AngouriMath.Numerics.CombinatorialFunctions.RisingFactorial(a, b); + // The Lambert W function on the branch the first argument names. + // https://github.com/asc-community/AngouriMath/issues/2195 + public static System.Numerics.Complex LambertW(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b); + public static double LambertW(double a, double b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b); + /// /// True where System.Numerics.Complex.IsNaN is, spelled out because that overload is /// .NET 7 and later and this assembly also targets netstandard2.0. A complex number is NaN @@ -534,6 +539,8 @@ public static bool IsNaN(System.Numerics.Complex a) { ("FallingFactorial", 2, typeof(double)), Def((a, b) => FallingFactorial(a, b)) }, { ("RisingFactorial", 2, typeof(System.Numerics.Complex)), Def((a, b) => RisingFactorial(a, b)) }, { ("RisingFactorial", 2, typeof(double)), Def((a, b) => RisingFactorial(a, b)) }, + { ("LambertW", 2, typeof(System.Numerics.Complex)), Def((a, b) => LambertW(a, b)) }, + { ("LambertW", 2, typeof(double)), Def((a, b) => LambertW(a, b)) }, }; // An expression tree is how C# spells "the MethodInfo of this method" without a string: diff --git a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt index b39e74b0a..4443f45db 100644 --- a/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt +++ b/Sources/AngouriMath/Functions/Compilation/IntoLinq/MathAllMethods.tt @@ -232,6 +232,11 @@ namespace AngouriMath.Core.Compilation.IntoLinq public static double <#= name #>(double a, double b) => AngouriMath.Numerics.CombinatorialFunctions.<#= name #>(a, b); <# } #> + // The Lambert W function on the branch the first argument names. + // https://github.com/asc-community/AngouriMath/issues/2195 + public static System.Numerics.Complex LambertW(System.Numerics.Complex a, System.Numerics.Complex b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b); + public static double LambertW(double a, double b) => AngouriMath.Numerics.LambertWFunction.LambertW(a, b); + /// /// True where System.Numerics.Complex.IsNaN is, spelled out because that overload is /// .NET 7 and later and this assembly also targets netstandard2.0. A complex number is NaN @@ -285,6 +290,9 @@ namespace AngouriMath.Core.Compilation.IntoLinq <# foreach (var type in new[]{ "System.Numerics.Complex", "double" }) { #> { ("<#= name #>", 2, typeof(<#= type #>)), Def<<#= type #>>((a, b) => <#= name #>(a, b)) }, <# } #> +<# } #> +<# foreach (var type in new[]{ "System.Numerics.Complex", "double" }) { #> + { ("LambertW", 2, typeof(<#= type #>)), Def<<#= type #>>((a, b) => LambertW(a, b)) }, <# } #> }; diff --git a/Sources/AngouriMath/Numerics/LambertWFunction.cs b/Sources/AngouriMath/Numerics/LambertWFunction.cs new file mode 100644 index 000000000..119a08036 --- /dev/null +++ b/Sources/AngouriMath/Numerics/LambertWFunction.cs @@ -0,0 +1,94 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System; +using NumericsComplex = System.Numerics.Complex; + +namespace AngouriMath.Numerics +{ + /// + /// The Lambert W function in double precision, for the compilers, on the branches the interpreter + /// takes: those of Corless et al. (1996), W_0 real on [-1/e, +oo) and W_(-1) on + /// [-1/e, 0), NaN on a branch that is not whole, and -oo at 0 off the principal one. + /// https://github.com/asc-community/AngouriMath/issues/2195 + /// + internal static class LambertWFunction + { + [AngouriMath.Core.ConstantField] private static readonly NumericsComplex NaN = new(double.NaN, 0); + + public static NumericsComplex LambertW(NumericsComplex branch, NumericsComplex z) + { + if (branch.Imaginary != 0 || double.IsNaN(branch.Real) || double.IsInfinity(branch.Real) + || Math.Floor(branch.Real) != branch.Real || Math.Abs(branch.Real) > int.MaxValue) + return NaN; + if (double.IsNaN(z.Real) || double.IsNaN(z.Imaginary) || double.IsInfinity(z.Real) || double.IsInfinity(z.Imaginary)) + return NaN; + var k = (int)branch.Real; + if (z == 0) + return k == 0 ? 0 : double.NegativeInfinity; + // Without a signed zero, which a computation can leave behind: z on the axis is approached from above. + if (z.Imaginary == 0) + z = new NumericsComplex(z.Real, 0); + + var nearTheBranchPoint = Math.E * z + 1; + // W_0 from either side, W_(-1) from above and on the axis, W_1 from below meet at -1/e. + var meetsTheBranchPoint = k == 0 || k == -1 && z.Imaginary >= 0 || k == 1 && z.Imaginary < 0; + // -1/e is not a double, and within its rounding the value is the branch point's. + if (meetsTheBranchPoint && NumericsComplex.Abs(nearTheBranchPoint) < 1e-15) + return -1; + var isReal = z.Imaginary == 0 && nearTheBranchPoint.Real >= 0 && (k == 0 || k == -1 && z.Real < 0); + + // The start, by the regions SciPy's lambertw takes on the principal branch: the series about the + // branch point within 0.3 of it (and within a quarter of it on the other branches that meet + // there), ln(1 + z) in a box right of -1 about the axis, and the asymptotic start elsewhere. ln(1 + z) + // is no start near -1, where it is ln 0 and its iteration reaches another branch, nor on the axis + // from -1 to -1/e, where it is real and W_0 is not. + NumericsComplex w; + var branchPointSeries = k == 0 ? NumericsComplex.Abs(z + 1 / Math.E) < 0.3 : meetsTheBranchPoint && NumericsComplex.Abs(nearTheBranchPoint) < 0.25; + if (branchPointSeries) + { + // p = sqrt(2 (e z + 1)), and w = -1 + p - p^2/3 + 11 p^3/72 on W_0, -p on the other branch. + var p = NumericsComplex.Sqrt(2 * nearTheBranchPoint); + if (k != 0) + p = -p; + w = -1 + p - p * p / 3 + 11 * p * p * p / 72; + } + else if (k == 0 && z.Real > -1 && z.Real < 1.5 && Math.Abs(z.Imaginary) < 1 && z.Real > -2.5 * Math.Abs(z.Imaginary) - 0.2) + w = NumericsComplex.Log(1 + z); + else + { + var l1 = NumericsComplex.Log(z) + new NumericsComplex(0, 2 * Math.PI * k); + var l2 = NumericsComplex.Log(l1); + w = l1 - l2 + l2 / l1; + } + if (isReal) + w = new NumericsComplex(w.Real, 0); + for (var step = 0; step < 64; step++) + { + // Halley: w - f / (e^w (w + 1) - (w + 2) f / (2 w + 2)), with f = w e^w - z. + var ew = NumericsComplex.Exp(w); + var f = w * ew - z; + if (w + 1 == 0) + break; + var change = f / (ew * (w + 1) - (w + 2) * f / (2 * w + 2)); + w -= change; + if (isReal) + w = new NumericsComplex(w.Real, 0); + if (NumericsComplex.Abs(change) <= 1e-15 * Math.Max(NumericsComplex.Abs(w), 1)) + break; + } + return w; + } + + // A real value where the branch is real there, and NaN where it is not. + public static double LambertW(double branch, double z) + { + var w = LambertW(new NumericsComplex(branch, 0), new NumericsComplex(z, 0)); + return w.Imaginary == 0 ? w.Real : double.NaN; + } + } +} diff --git a/Sources/Tests/AotSmokeTest/Program.cs b/Sources/Tests/AotSmokeTest/Program.cs index f856b05f4..23563ef3f 100644 --- a/Sources/Tests/AotSmokeTest/Program.cs +++ b/Sources/Tests/AotSmokeTest/Program.cs @@ -309,6 +309,8 @@ private static void Compilation() var linqBinomial = "binomial(x, 2) + fallingfactorial(x, 3) + risingfactorial(x, 2) + x!!".Compile(x); Check("compiled binomial(x, 2) + x(x-1)(x-2) + x(x+1) + x!! at x = 5", fastBinomial.Call(5) == 10 + 60 + 30 + 15 && linqBinomial(5) == 10 + 60 + 30 + 15, $"{fastBinomial.Call(5)}, {linqBinomial(5)}"); + var compiledW = "lambertw(-1, x)".Compile(x); + CheckClose("compiled lambertw(-1, x) at x = -0.3", compiledW(-0.3), -1.7813370234216276); // FastExpression: a statement is its truth value, and a piecewise the case that holds. var fastPiecewise = "piecewise(x provided x > 0, -x)".Compile(x); var fastStatement = "x > 1 or x < 0".Compile(x); diff --git a/Sources/Tests/UnitTests/Common/LambertWCompilationTest.cs b/Sources/Tests/UnitTests/Common/LambertWCompilationTest.cs new file mode 100644 index 000000000..47dd6ed0b --- /dev/null +++ b/Sources/Tests/UnitTests/Common/LambertWCompilationTest.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System.Numerics; +using AngouriMath; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Common +{ + /// + /// The Lambert W function compiles, in FastExpression and in the LINQ compiler, to what evaluation + /// gives on each branch: near the branch point -1/e, on either side of it, on the negative + /// axis where the branches are complex, and off the axis. It was refused. + /// #2195 + /// + [Trait("Area", "Common")] + public sealed class LambertWCompilationTest + { + private static readonly string[] branches = { "-2", "-1", "0", "1", "3", "1/2" }; + + private static readonly string[] arguments = + { + "0", "1", "2", "10", "100000", "-1/10", "-3/10", "-9/25", "-1/e", "-37/100", "-1/2", "-2", + "i", "1 + i", "-1 - i", "i/2", "-3 + i/10", "-3/10 - i/100", "-3/10 + i/100", + }; + + private static Complex Numeric(string value) => ((Entity.Number.Complex)value.ToEntity().Evaled).ToNumerics(); + + // Both NaN, both the same infinity, or within a part in a billion. + private static bool Agree(Complex expected, Complex actual) + => double.IsNaN(expected.Real) || double.IsNaN(expected.Imaginary) + ? double.IsNaN(actual.Real) || double.IsNaN(actual.Imaginary) + : double.IsInfinity(expected.Real) + ? expected == actual + : Complex.Abs(expected - actual) <= 1e-9 * System.Math.Max(1, Complex.Abs(expected)); + + [Fact] + public void ItIsWhatEvaluationSaysOnEachBranch() + { + var fast = "lambertw(k, z)".ToEntity().Compile("k", "z"); + var linq = "lambertw(k, z)".ToEntity().Compile("k", "z"); + var real = "lambertw(k, z)".ToEntity().Compile("k", "z"); + foreach (var branch in branches) + foreach (var argument in arguments) + { + var evaluated = Assert.IsAssignableFrom($"lambertw({branch}, {argument})".ToEntity().Evaled); + var expected = evaluated.ToNumerics(); + var (k, z) = (Numeric(branch), Numeric(argument)); + Assert.True(Agree(expected, fast.Call(k, z)), $"lambertw({branch}, {argument}): {expected} evaluated, {fast.Call(k, z)} by FastExpression"); + Assert.True(Agree(expected, linq(k, z)), $"lambertw({branch}, {argument}): {expected} evaluated, {linq(k, z)} by LINQ"); + if (z.Imaginary == 0) + { + // Real where the imaginary part is rounding's: evaluation rounds -1/e to a decimal + // that is across the branch point by a digit, and leaves an imaginary part of that size. + var overTheReals = System.Math.Abs(expected.Imaginary) <= 1e-12 * System.Math.Max(1, Complex.Abs(expected)) + ? new Complex(expected.Real, 0) : new Complex(double.NaN, 0); + Assert.True(Agree(overTheReals, real(k.Real, z.Real)), $"lambertw({branch}, {argument}): {overTheReals} over the reals, {real(k.Real, z.Real)} over double"); + } + } + } + + [Fact] + public void ThePrincipalBranchCompilesInOneArgument() + => Assert.Equal(0.5671432904097838, "lambertw(x)".ToEntity().Compile("x")(1), 15); + } +} diff --git a/Sources/Tests/UnitTests/Convenience/LambertWTest.cs b/Sources/Tests/UnitTests/Convenience/LambertWTest.cs index cd4c2acd4..6efa26053 100644 --- a/Sources/Tests/UnitTests/Convenience/LambertWTest.cs +++ b/Sources/Tests/UnitTests/Convenience/LambertWTest.cs @@ -24,7 +24,8 @@ public sealed class LambertWTest /// /// mpmath's lambertw at 60 digits, at the decimals written: on the real axis where each branch is real, across the cut /// below -1/e where W_0 and W_(-1) are conjugates, about the branch point from above and below, - /// and far out. + /// and far out. W_0 on the axis from -1 to -2/e, about -1, and just left of -1 was real, or on + /// another branch: its start was ln(1 + z), which is real there and is ln 0 at -1. /// public static readonly TheoryData Points = new() { @@ -50,6 +51,11 @@ public sealed class LambertWTest { 0, "0.5", "0.5", "0.40431612353121278901924224143642299", "0.24343775688425395570266928474288716" }, { 0, "-0.2", "0.3", "-0.079775717127478451154646095753164503", "0.38226147065890346680498485830193446" }, { 0, "2", "-1.5", "0.93210073009396256291394339557696040", "-0.31632723076474133978590653951403091" }, + { 0, "-0.8", "0", "-0.47296437953376993647110619289081", "1.1934970731363015053999934286237" }, + { 0, "-0.975", "0", "-0.33577732103000080461145009761745", "1.3220782205206208727604624558714" }, + { 0, "-1", "0", "-0.31813150520476413531265425158766", "1.3372357014306894089011621431937" }, + { 0, "-1.02", "0", "-0.30431558075585315366696810619974", "1.3489097881249077058064917902432" }, + { 0, "-1.349", "-0.358", "0.039601766331215229042972440561754", "-1.3409186442644250944627990020702" }, { -1, "-0.1", "0", "-3.5771520639572972184093919635119949", "0.0" }, { -1, "-0.3", "0", "-1.7813370234216276119741702815127453", "0.0" }, { -1, "-0.36", "0", "-1.2227701339785059531429380734238623", "0.0" },